@atlaskit/editor-plugin-autocomplete 3.6.1 → 3.6.2

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 (32) hide show
  1. package/CANONICAL_FIX__DO_NOT_USE_ME_A/package.json +1 -8
  2. package/CANONICAL_FIX__DO_NOT_USE_ME_B/package.json +1 -8
  3. package/CANONICAL_FIX__DO_NOT_USE_ME_C/package.json +1 -8
  4. package/CHANGELOG.md +10 -0
  5. package/autocompletePlugin/package.json +1 -8
  6. package/autocompletePluginType/package.json +1 -8
  7. package/dist/cjs/pm-plugins/autocomplete-plugin.js +14 -0
  8. package/dist/es2019/pm-plugins/autocomplete-plugin.js +14 -0
  9. package/dist/esm/pm-plugins/autocomplete-plugin.js +14 -0
  10. package/package.json +2 -2
  11. package/src/pm-plugins/autocomplete-plugin/package.json +1 -8
  12. package/src/pm-plugins/autocomplete-plugin.ts +25 -12
  13. package/src/pm-plugins/local-slow-lane-client.ts +6 -14
  14. package/src/pm-plugins/slow-lane-client/package.json +1 -8
  15. package/src/pm-plugins/text-predictor/package.json +1 -8
  16. package/src/pm-plugins/text-predictor.ts +4 -1
  17. package/dist/types-ts4.5/analytics/ufo.d.ts +0 -38
  18. package/dist/types-ts4.5/autocompletePlugin.d.ts +0 -2
  19. package/dist/types-ts4.5/autocompletePluginType.d.ts +0 -10
  20. package/dist/types-ts4.5/entry-points/autocompletePlugin.d.ts +0 -1
  21. package/dist/types-ts4.5/entry-points/autocompletePluginType.d.ts +0 -1
  22. package/dist/types-ts4.5/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +0 -2
  23. package/dist/types-ts4.5/entry-points/src-pm-plugins-slow-lane-client.d.ts +0 -2
  24. package/dist/types-ts4.5/entry-points/src-pm-plugins-text-predictor.d.ts +0 -2
  25. package/dist/types-ts4.5/index.d.ts +0 -2
  26. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +0 -49
  27. package/dist/types-ts4.5/pm-plugins/debug-mode.d.ts +0 -27
  28. package/dist/types-ts4.5/pm-plugins/ghost-text-decoration.d.ts +0 -7
  29. package/dist/types-ts4.5/pm-plugins/local-slow-lane-client.d.ts +0 -241
  30. package/dist/types-ts4.5/pm-plugins/scoring-pipeline.d.ts +0 -43
  31. package/dist/types-ts4.5/pm-plugins/slow-lane-client.d.ts +0 -46
  32. package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +0 -91
@@ -4,12 +4,5 @@
4
4
  "module": "../dist/esm/pm-plugins/autocomplete-plugin.js",
5
5
  "module:es2019": "../dist/es2019/pm-plugins/autocomplete-plugin.js",
6
6
  "sideEffects": false,
7
- "types": "../dist/types/pm-plugins/autocomplete-plugin.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../dist/types/pm-plugins/autocomplete-plugin.d.ts"
15
8
  }
@@ -4,12 +4,5 @@
4
4
  "module": "../dist/esm/pm-plugins/slow-lane-client.js",
5
5
  "module:es2019": "../dist/es2019/pm-plugins/slow-lane-client.js",
6
6
  "sideEffects": false,
7
- "types": "../dist/types/pm-plugins/slow-lane-client.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../dist/types-ts4.5/pm-plugins/slow-lane-client.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../dist/types/pm-plugins/slow-lane-client.d.ts"
15
8
  }
@@ -4,12 +4,5 @@
4
4
  "module": "../dist/esm/pm-plugins/text-predictor.js",
5
5
  "module:es2019": "../dist/es2019/pm-plugins/text-predictor.js",
6
6
  "sideEffects": false,
7
- "types": "../dist/types/pm-plugins/text-predictor.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../dist/types-ts4.5/pm-plugins/text-predictor.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../dist/types/pm-plugins/text-predictor.d.ts"
15
8
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @atlaskit/editor-plugin-autocomplete
2
2
 
3
+ ## 3.6.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`8ed22a585196f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8ed22a585196f) -
8
+ [ux] Fixes a bug where autocomplete suggestions were shown when the cursor was positioned in the
9
+ middle of an existing word. Suggestions are now suppressed unless the cursor is at the trailing
10
+ edge of a token.
11
+ - Updated dependencies
12
+
3
13
  ## 3.6.1
4
14
 
5
15
  ### Patch Changes
@@ -4,12 +4,5 @@
4
4
  "module": "../dist/esm/entry-points/autocompletePlugin.js",
5
5
  "module:es2019": "../dist/es2019/entry-points/autocompletePlugin.js",
6
6
  "sideEffects": false,
7
- "types": "../dist/types/entry-points/autocompletePlugin.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../dist/types-ts4.5/entry-points/autocompletePlugin.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../dist/types/entry-points/autocompletePlugin.d.ts"
15
8
  }
@@ -4,12 +4,5 @@
4
4
  "module": "../dist/esm/entry-points/autocompletePluginType.js",
5
5
  "module:es2019": "../dist/es2019/entry-points/autocompletePluginType.js",
6
6
  "sideEffects": false,
7
- "types": "../dist/types/entry-points/autocompletePluginType.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../dist/types-ts4.5/entry-points/autocompletePluginType.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../dist/types/entry-points/autocompletePluginType.d.ts"
15
8
  }
@@ -435,11 +435,25 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
435
435
  }
436
436
  debounceTimer = setTimeout(function () {
437
437
  try {
438
+ var _nodeAfter$text;
438
439
  var state = view.state;
439
440
  var selection = state.selection;
440
441
  if (!selection.empty) {
441
442
  return;
442
443
  }
444
+
445
+ // Suppress suggestions when the cursor is mid-word — only offer
446
+ // completions when the cursor is at the trailing edge of a token.
447
+ // nodeAfter correctly handles inline atoms (mentions, emojis) where
448
+ // parentOffset and textContent indices diverge.
449
+ var nodeAfter = selection.$from.nodeAfter;
450
+ var charAfterCursor = nodeAfter !== null && nodeAfter !== void 0 && nodeAfter.isText ? (_nodeAfter$text = nodeAfter.text) === null || _nodeAfter$text === void 0 ? void 0 : _nodeAfter$text[0] : undefined;
451
+ // Only suppress for Unicode letters, digits, and underscore — hyphens,
452
+ // apostrophes, and similar punctuation are valid left-edge boundaries
453
+ // and should not block suggestions (e.g. cursor before '-' in compound-word).
454
+ if (charAfterCursor && /(?:[0-9A-Z_a-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088F\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5C\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDC-\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C8A\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7DC\uA7F1-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDDC0-\uDDF3\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD40-\uDD59\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDD40-\uDD65\uDD6F-\uDD85\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDEC2-\uDEC7\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE3F\uDE40\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61\uDF80-\uDF89\uDF8B\uDF8E\uDF90-\uDFB5\uDFB7\uDFD1\uDFD3]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDED0-\uDEE3\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8\uDFC0-\uDFE0\uDFF0-\uDFF9]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDDB0-\uDDDB\uDDE0-\uDDE9\uDEE0-\uDEF2\uDF02\uDF04-\uDF10\uDF12-\uDF33\uDF50-\uDF59\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD80E\uD80F\uD81C-\uD822\uD840-\uD868\uD86A-\uD86D\uD86F-\uD872\uD874-\uD879\uD880-\uD883\uD885-\uD88C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2F\uDC41-\uDC46\uDC60-\uDFFF]|\uD810[\uDC00-\uDFFA]|\uD811[\uDC00-\uDE46]|\uD818[\uDD00-\uDD1D\uDD30-\uDD39]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDD40-\uDD6C\uDD70-\uDD79\uDE40-\uDE96\uDEA0-\uDEB8\uDEBB-\uDED3\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3\uDFF2-\uDFF6]|\uD823[\uDC00-\uDCD5\uDCFF-\uDD1E\uDD80-\uDDF2]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD32\uDD50-\uDD52\uDD55\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD833[\uDCF0-\uDCF9]|\uD834[\uDEC0-\uDED3\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E\uDF25-\uDF2A]|\uD838[\uDC30-\uDC6D\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDCD0-\uDCEB\uDCF0-\uDCF9\uDDD0-\uDDED\uDDF0-\uDDFA\uDEC0-\uDEDE\uDEE0-\uDEE2\uDEE4\uDEE5\uDEE7-\uDEED\uDEF0-\uDEF4\uDEFE\uDEFF\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEAD\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0\uDFF0-\uDFFF]|\uD87B[\uDC00-\uDE5D]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A\uDF50-\uDFFF]|\uD88D[\uDC00-\uDC79])/.test(charAfterCursor)) {
455
+ return;
456
+ }
443
457
  var textBefore = getTextBeforeCursor(state);
444
458
 
445
459
  // Suppress re-showing the same suggestion the user just dismissed.
@@ -415,6 +415,7 @@ export const createAutocompletePlugin = (options, api) => {
415
415
  }
416
416
  debounceTimer = setTimeout(() => {
417
417
  try {
418
+ var _nodeAfter$text;
418
419
  const {
419
420
  state
420
421
  } = view;
@@ -424,6 +425,19 @@ export const createAutocompletePlugin = (options, api) => {
424
425
  if (!selection.empty) {
425
426
  return;
426
427
  }
428
+
429
+ // Suppress suggestions when the cursor is mid-word — only offer
430
+ // completions when the cursor is at the trailing edge of a token.
431
+ // nodeAfter correctly handles inline atoms (mentions, emojis) where
432
+ // parentOffset and textContent indices diverge.
433
+ const nodeAfter = selection.$from.nodeAfter;
434
+ const charAfterCursor = nodeAfter !== null && nodeAfter !== void 0 && nodeAfter.isText ? (_nodeAfter$text = nodeAfter.text) === null || _nodeAfter$text === void 0 ? void 0 : _nodeAfter$text[0] : undefined;
435
+ // Only suppress for Unicode letters, digits, and underscore — hyphens,
436
+ // apostrophes, and similar punctuation are valid left-edge boundaries
437
+ // and should not block suggestions (e.g. cursor before '-' in compound-word).
438
+ if (charAfterCursor && /[\p{L}\p{N}_]/u.test(charAfterCursor)) {
439
+ return;
440
+ }
427
441
  const textBefore = getTextBeforeCursor(state);
428
442
 
429
443
  // Suppress re-showing the same suggestion the user just dismissed.
@@ -428,11 +428,25 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
428
428
  }
429
429
  debounceTimer = setTimeout(function () {
430
430
  try {
431
+ var _nodeAfter$text;
431
432
  var state = view.state;
432
433
  var selection = state.selection;
433
434
  if (!selection.empty) {
434
435
  return;
435
436
  }
437
+
438
+ // Suppress suggestions when the cursor is mid-word — only offer
439
+ // completions when the cursor is at the trailing edge of a token.
440
+ // nodeAfter correctly handles inline atoms (mentions, emojis) where
441
+ // parentOffset and textContent indices diverge.
442
+ var nodeAfter = selection.$from.nodeAfter;
443
+ var charAfterCursor = nodeAfter !== null && nodeAfter !== void 0 && nodeAfter.isText ? (_nodeAfter$text = nodeAfter.text) === null || _nodeAfter$text === void 0 ? void 0 : _nodeAfter$text[0] : undefined;
444
+ // Only suppress for Unicode letters, digits, and underscore — hyphens,
445
+ // apostrophes, and similar punctuation are valid left-edge boundaries
446
+ // and should not block suggestions (e.g. cursor before '-' in compound-word).
447
+ if (charAfterCursor && /(?:[0-9A-Z_a-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088F\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5C\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDC-\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C8A\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7DC\uA7F1-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDDC0-\uDDF3\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD40-\uDD59\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDD40-\uDD65\uDD6F-\uDD85\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDEC2-\uDEC7\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE3F\uDE40\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61\uDF80-\uDF89\uDF8B\uDF8E\uDF90-\uDFB5\uDFB7\uDFD1\uDFD3]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDED0-\uDEE3\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8\uDFC0-\uDFE0\uDFF0-\uDFF9]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDDB0-\uDDDB\uDDE0-\uDDE9\uDEE0-\uDEF2\uDF02\uDF04-\uDF10\uDF12-\uDF33\uDF50-\uDF59\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD80E\uD80F\uD81C-\uD822\uD840-\uD868\uD86A-\uD86D\uD86F-\uD872\uD874-\uD879\uD880-\uD883\uD885-\uD88C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2F\uDC41-\uDC46\uDC60-\uDFFF]|\uD810[\uDC00-\uDFFA]|\uD811[\uDC00-\uDE46]|\uD818[\uDD00-\uDD1D\uDD30-\uDD39]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDD40-\uDD6C\uDD70-\uDD79\uDE40-\uDE96\uDEA0-\uDEB8\uDEBB-\uDED3\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3\uDFF2-\uDFF6]|\uD823[\uDC00-\uDCD5\uDCFF-\uDD1E\uDD80-\uDDF2]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD32\uDD50-\uDD52\uDD55\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD833[\uDCF0-\uDCF9]|\uD834[\uDEC0-\uDED3\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E\uDF25-\uDF2A]|\uD838[\uDC30-\uDC6D\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDCD0-\uDCEB\uDCF0-\uDCF9\uDDD0-\uDDED\uDDF0-\uDDFA\uDEC0-\uDEDE\uDEE0-\uDEE2\uDEE4\uDEE5\uDEE7-\uDEED\uDEF0-\uDEF4\uDEFE\uDEFF\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEAD\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0\uDFF0-\uDFFF]|\uD87B[\uDC00-\uDE5D]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A\uDF50-\uDFFF]|\uD88D[\uDC00-\uDC79])/.test(charAfterCursor)) {
448
+ return;
449
+ }
436
450
  var textBefore = getTextBeforeCursor(state);
437
451
 
438
452
  // Suppress re-showing the same suggestion the user just dismissed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-autocomplete",
3
- "version": "3.6.1",
3
+ "version": "3.6.2",
4
4
  "description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -35,7 +35,7 @@
35
35
  "wink-nlp": "^2.4.0"
36
36
  },
37
37
  "peerDependencies": {
38
- "@atlaskit/editor-common": "^115.15.0",
38
+ "@atlaskit/editor-common": "^115.16.0",
39
39
  "@atlaskit/editor-plugin-analytics": "^11.0.0",
40
40
  "react": "^18.2.0"
41
41
  },
@@ -4,12 +4,5 @@
4
4
  "module": "../../../dist/esm/entry-points/src-pm-plugins-autocomplete-plugin.js",
5
5
  "module:es2019": "../../../dist/es2019/entry-points/src-pm-plugins-autocomplete-plugin.js",
6
6
  "sideEffects": false,
7
- "types": "../../../dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../../../dist/types-ts4.5/entry-points/src-pm-plugins-autocomplete-plugin.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../../../dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts"
15
8
  }
@@ -514,6 +514,19 @@ export const createAutocompletePlugin = (
514
514
  return;
515
515
  }
516
516
 
517
+ // Suppress suggestions when the cursor is mid-word — only offer
518
+ // completions when the cursor is at the trailing edge of a token.
519
+ // nodeAfter correctly handles inline atoms (mentions, emojis) where
520
+ // parentOffset and textContent indices diverge.
521
+ const nodeAfter = selection.$from.nodeAfter;
522
+ const charAfterCursor = nodeAfter?.isText ? nodeAfter.text?.[0] : undefined;
523
+ // Only suppress for Unicode letters, digits, and underscore — hyphens,
524
+ // apostrophes, and similar punctuation are valid left-edge boundaries
525
+ // and should not block suggestions (e.g. cursor before '-' in compound-word).
526
+ if (charAfterCursor && /[\p{L}\p{N}_]/u.test(charAfterCursor)) {
527
+ return;
528
+ }
529
+
517
530
  const textBefore = getTextBeforeCursor(state);
518
531
 
519
532
  // Suppress re-showing the same suggestion the user just dismissed.
@@ -680,20 +693,20 @@ export const createAutocompletePlugin = (
680
693
  }
681
694
  return false;
682
695
  },
683
- focus: () => {
684
- loadDefaultVocabulary({ isLocalLLM: options?.useLocalModel ?? false }).catch((error) => {
685
- logException(error as Error, {
686
- location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
696
+ focus: () => {
697
+ loadDefaultVocabulary({ isLocalLLM: options?.useLocalModel ?? false }).catch((error) => {
698
+ logException(error as Error, {
699
+ location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
700
+ });
687
701
  });
688
- });
689
- loadVectorsAsync({
690
- getBinaryUrl: options?.getVectorsBinaryUrl,
691
- isLocalLLM: options?.useLocalModel ?? false,
692
- }).catch((error) => {
693
- logException(error as Error, {
694
- location: 'editor-plugin-autocomplete/loadVectorsAsync',
702
+ loadVectorsAsync({
703
+ getBinaryUrl: options?.getVectorsBinaryUrl,
704
+ isLocalLLM: options?.useLocalModel ?? false,
705
+ }).catch((error) => {
706
+ logException(error as Error, {
707
+ location: 'editor-plugin-autocomplete/loadVectorsAsync',
708
+ });
695
709
  });
696
- });
697
710
  if (!hasIngestedPage) {
698
711
  hasIngestedPage = true;
699
712
  refreshContext({
@@ -367,16 +367,14 @@ let bePayloadDataPromise: Promise<void> | undefined;
367
367
  * :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
368
368
  * :returns: The parsed JSON value, or `null` if neither interop mode applies.
369
369
  */
370
- const unwrapJsonModule = <T,>(mod: unknown, shape: 'object' | 'array'): T | null => {
370
+ const unwrapJsonModule = <T>(mod: unknown, shape: 'object' | 'array'): T | null => {
371
371
  if (mod == null || typeof mod !== 'object') {
372
372
  return null;
373
373
  }
374
374
  const namespace = mod as Record<string, unknown> & { default?: unknown };
375
375
 
376
376
  // Compute the named-export own-keys (strip synthetic markers).
377
- const ownKeys = Object.keys(namespace).filter(
378
- (k) => k !== 'default' && k !== '__esModule',
379
- );
377
+ const ownKeys = Object.keys(namespace).filter((k) => k !== 'default' && k !== '__esModule');
380
378
 
381
379
  // PREFER named exports when present — they always reflect the JSON's real
382
380
  // top-level keys / indices, regardless of what `default` happens to be.
@@ -456,10 +454,7 @@ const loadBePayloadData = (): Promise<void> => {
456
454
  }
457
455
 
458
456
  firstTokenToWords = new Map(
459
- Object.entries(firstTokenToWordsData).map(([tokenId, words]) => [
460
- Number(tokenId),
461
- words,
462
- ]),
457
+ Object.entries(firstTokenToWordsData).map(([tokenId, words]) => [Number(tokenId), words]),
463
458
  );
464
459
  l2Words = new Set(Object.keys(vocabularyData.words));
465
460
  prefixMapTokenIds = Array.from(firstTokenToWords.keys());
@@ -519,7 +514,6 @@ export const computeBePayload = (
519
514
  */
520
515
  validTokenIds: number[] = prefixMapTokenIds,
521
516
  ): Record<string, number> => {
522
-
523
517
  // 1. Numerically-stable masked softmax over validTokenIds only.
524
518
  let maxLogit = -Infinity;
525
519
  for (const id of validTokenIds) {
@@ -1006,7 +1000,7 @@ export const createLocalSlowLaneClient = (
1006
1000
  const lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
1007
1001
  const semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
1008
1002
  const arcticInput = wrapForArctic(semanticText);
1009
- const captureCompletionTime = <T,>(
1003
+ const captureCompletionTime = <T>(
1010
1004
  promise: Promise<T>,
1011
1005
  onResolved: (resolvedAt: number) => void,
1012
1006
  ): Promise<T> =>
@@ -1043,8 +1037,7 @@ export const createLocalSlowLaneClient = (
1043
1037
  max_tokens: 1,
1044
1038
  temperature: 0,
1045
1039
  logprobs: false,
1046
- })
1047
- ,
1040
+ }),
1048
1041
  (resolvedAt) => {
1049
1042
  tLmDone = resolvedAt;
1050
1043
  },
@@ -1053,8 +1046,7 @@ export const createLocalSlowLaneClient = (
1053
1046
  engine.embeddings.create({
1054
1047
  model: LOCAL_MLC_EMBEDDING_MODEL_ID,
1055
1048
  input: arcticInput,
1056
- })
1057
- ,
1049
+ }),
1058
1050
  (resolvedAt) => {
1059
1051
  tEmbDone = resolvedAt;
1060
1052
  },
@@ -4,12 +4,5 @@
4
4
  "module": "../../../dist/esm/entry-points/src-pm-plugins-slow-lane-client.js",
5
5
  "module:es2019": "../../../dist/es2019/entry-points/src-pm-plugins-slow-lane-client.js",
6
6
  "sideEffects": false,
7
- "types": "../../../dist/types/entry-points/src-pm-plugins-slow-lane-client.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../../../dist/types-ts4.5/entry-points/src-pm-plugins-slow-lane-client.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../../../dist/types/entry-points/src-pm-plugins-slow-lane-client.d.ts"
15
8
  }
@@ -4,12 +4,5 @@
4
4
  "module": "../../../dist/esm/entry-points/src-pm-plugins-text-predictor.js",
5
5
  "module:es2019": "../../../dist/es2019/entry-points/src-pm-plugins-text-predictor.js",
6
6
  "sideEffects": false,
7
- "types": "../../../dist/types/entry-points/src-pm-plugins-text-predictor.d.ts",
8
- "typesVersions": {
9
- ">=4.5 <5.9": {
10
- "*": [
11
- "../../../dist/types-ts4.5/entry-points/src-pm-plugins-text-predictor.d.ts"
12
- ]
13
- }
14
- }
7
+ "types": "../../../dist/types/entry-points/src-pm-plugins-text-predictor.d.ts"
15
8
  }
@@ -889,7 +889,10 @@ export const loadDefaultVocabulary = (options?: { isLocalLLM?: boolean }): Promi
889
889
  l3WordCount: l3VocabularyData.length,
890
890
  });
891
891
  } catch (e) {
892
- failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', { isLocalLLM, errorType: 'parse_error' });
892
+ failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
893
+ isLocalLLM,
894
+ errorType: 'parse_error',
895
+ });
893
896
  // Allow a later call to retry the load rather than caching the failure.
894
897
  vocabularyLoadPromise = undefined;
895
898
  throw e;
@@ -1,38 +0,0 @@
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
- import { type CustomData } from '@atlaskit/ufo';
14
- /**
15
- * Experience name strings are surfaced downstream by the UFO pipeline as
16
- * `platform.fe.<type>.<platform.component>.<name>` (e.g.
17
- * `platform.fe.operation.editor-plugin-autocomplete.slow-lane-fetch`), so
18
- * names here should be short, dot-free, and must not repeat the component
19
- * name. This matches the convention used by `@atlaskit/emoji`'s
20
- * `ufoExperiences` map (e.g. `'emoji-rendered'`).
21
- */
22
- /**
23
- * Only experiences with meaningful async work and a real success/failure
24
- * outcome belong on UFO (which measures latency and success rate for FE
25
- * Reliability SLOs). Per-keystroke suggestion lifecycle counters (viewed /
26
- * inserted / dismissed) are tracked via analytics-next instead — they would
27
- * otherwise emit zero-duration events on every word boundary.
28
- */
29
- export declare const EXPERIENCE_NAME: {
30
- readonly SLOW_LANE_FETCH: "slow-lane-fetch";
31
- readonly LOAD_VOCABULARY: "load-vocabulary";
32
- readonly LOAD_VECTORS: "load-vectors";
33
- };
34
- export type AutocompleteExperienceName = (typeof EXPERIENCE_NAME)[keyof typeof EXPERIENCE_NAME];
35
- export declare const startExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
36
- export declare const succeedExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
37
- export declare const failExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
38
- export declare const abortExp: (name: AutocompleteExperienceName, id: string, reason?: string) => void;
@@ -1,2 +0,0 @@
1
- import type { AutocompletePlugin } from './autocompletePluginType';
2
- export declare const autocompletePlugin: AutocompletePlugin;
@@ -1,10 +0,0 @@
1
- import type { NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
2
- import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
3
- import type { AutocompletePluginOptions, AutocompletePluginState } from './pm-plugins/autocomplete-plugin';
4
- export type AutocompletePlugin = NextEditorPlugin<'autocomplete', {
5
- pluginConfiguration?: AutocompletePluginOptions | undefined;
6
- sharedState: AutocompletePluginState | undefined;
7
- dependencies: [
8
- OptionalPlugin<AnalyticsPlugin>
9
- ];
10
- }>;
@@ -1 +0,0 @@
1
- export { autocompletePlugin } from '../autocompletePlugin';
@@ -1 +0,0 @@
1
- export type { AutocompletePlugin } from '../autocompletePluginType';
@@ -1,2 +0,0 @@
1
- export { autocompletePluginKey, createAutocompletePlugin } from '../pm-plugins/autocomplete-plugin';
2
- export type { AutocompleteContext, AutocompletePluginOptions, AutocompletePluginState, } from '../pm-plugins/autocomplete-plugin';
@@ -1,2 +0,0 @@
1
- export { createSlowLaneClient, getStoredContextVector, getStoredLmLogits, isWordBoundary, setDefaultSlowLaneClient, } from '../pm-plugins/slow-lane-client';
2
- export type { SlowLaneClientConfig, TypeaheadEncodingsRequest, TypeaheadEncodingsResponse, } from '../pm-plugins/slow-lane-client';
@@ -1,2 +0,0 @@
1
- export { getLastPredictionDebug, getPredictorStatus, incrementSessionFreq, ingestDocumentPage, initL3Vocabulary, initVectors, initVocabulary, loadDefaultVocabulary, loadVectorsAsync, predict, } from '../pm-plugins/text-predictor';
2
- export type { TenantVocabulary, WeightedTerm } from '../pm-plugins/text-predictor';
@@ -1,2 +0,0 @@
1
- export { autocompletePlugin } from './autocompletePlugin';
2
- export type { AutocompletePlugin } from './autocompletePluginType';
@@ -1,49 +0,0 @@
1
- import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
- import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
3
- import { PluginKey } from '@atlaskit/editor-prosemirror/state';
4
- import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
5
- import type { AutocompletePlugin } from '../autocompletePluginType';
6
- export declare const autocompletePluginKey: PluginKey;
7
- export interface AutocompletePluginState {
8
- /** The decoration set containing the ghost text widget */
9
- decorationSet: DecorationSet;
10
- /** The document position where the ghost text should appear */
11
- ghostPosition: number;
12
- /** The predicted ghost text to display */
13
- ghostText: string;
14
- }
15
- /**
16
- * Context provided to the autocomplete plugin on first editor focus.
17
- * Text fields are selectively ingested to boost word-frequency scoring for
18
- * predictions, giving words already present in the document/thread an L1
19
- * priority boost.
20
- */
21
- export interface AutocompleteContext {
22
- /** Full page content as a string (e.g. markdown). */
23
- fullPageContent?: string;
24
- /** The currently selected text on the page, if any. */
25
- pageSelectionContent?: string;
26
- /** Content of the parent comment when the editor is in reply or edit mode. */
27
- parentCommentContent?: string;
28
- /** Contents of sibling comments when the editor is in reply or edit mode. */
29
- siblingCommentsContents?: string[];
30
- }
31
- export interface AutocompletePluginOptions {
32
- /**
33
- * Async function called once on first editor focus to retrieve context for
34
- * word-frequency boosting. Called lazily so the preset can remain synchronous.
35
- */
36
- getContext?: () => Promise<AutocompleteContext | undefined>;
37
- /**
38
- * Async function that resolves to a URL for the word vectors binary file.
39
- * When provided, this takes precedence over the bundled asset URL.
40
- * Use this to serve vectors from a CDN or media service in production.
41
- */
42
- getVectorsBinaryUrl?: () => Promise<string>;
43
- /**
44
- * When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
45
- * the network-based slow-lane backend. Defaults to false (network client).
46
- */
47
- useLocalModel?: boolean;
48
- }
49
- export declare const createAutocompletePlugin: (options?: AutocompletePluginOptions, api?: ExtractInjectionAPI<AutocompletePlugin>) => SafePlugin<AutocompletePluginState>;
@@ -1,27 +0,0 @@
1
- /**
2
- * Contextual Typeahead Completions (CTC) debug logging utility.
3
- *
4
- * Logs are silent by default. Enable in any environment (dev, staging, prod):
5
- *
6
- * // Live, for the current session (no reload required):
7
- * __atlCtcDebug__.enable()
8
- *
9
- * // To disable:
10
- * __atlCtcDebug__.disable()
11
- *
12
- * // From initial page load (survives reload) — append to the URL:
13
- * ?atlCtcDebug=1
14
- *
15
- * Storage-free by design: avoids browser-storage consent controls (BSC), which can
16
- * block uncategorized localStorage/sessionStorage/cookie writes in some products.
17
- */
18
- declare global {
19
- interface Window {
20
- __atlCtcDebug__?: {
21
- enable: () => void;
22
- disable: () => void;
23
- isEnabled: () => boolean;
24
- };
25
- }
26
- }
27
- export declare const isAutocompleteDebugEnabled: () => boolean;
@@ -1,7 +0,0 @@
1
- import type { EditorState } from '@atlaskit/editor-prosemirror/state';
2
- import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
3
- /**
4
- * Creates a DecorationSet containing a ghost text widget at the given position.
5
- * The ghost text is rendered as a styled <span> that appears after the cursor.
6
- */
7
- export declare const createGhostTextDecorationSet: (state: EditorState, position: number, text: string) => DecorationSet;
@@ -1,241 +0,0 @@
1
- /**
2
- * Local Slow Lane Client: On-device inference via @mlc-ai/web-llm.
3
- *
4
- * Drop-in replacement for the network-based slow-lane-client. Instead of calling
5
- * a backend API, this client runs two models in the browser via WebGPU, in a
6
- * single MLCEngine, to reproduce the BE encoder's outputs on-device:
7
- *
8
- * - Causal LM (SmolLM2-135M-Instruct): one decode step per word boundary. A
9
- * registered LogitProcessor captures the raw next-token logits, which
10
- * `computeBePayload` turns into a whole-word `lm_logits` payload — a faithful
11
- * port of the BE `CausalLMEncoder._get_top_k_probs` (masked softmax over the
12
- * vocab's first-tokens, prefix expansion, L2 reservation, log-space pooling).
13
- * - Semantic embedder (Snowflake Arctic Embed S): produces the real 384-d
14
- * `semantic_vector`. Inputs are wrapped as passages (see `wrapForArctic`) so
15
- * the runtime vector lands in the same space as the precomputed word bin.
16
- *
17
- * ── Why main thread (no Web Worker)? ─────────────────────────────────────
18
- * The models are small enough (~640 MB combined VRAM) that WebGPU inference on
19
- * the main thread is viable:
20
- *
21
- * - WebGPU GPU compute is inherently async (doesn't block the main thread)
22
- * - CPU overhead (BE-parity post-processing) is a few ms
23
- * - Per-inference latency is well within autocomplete expectations
24
- * (~250 ms between word boundaries)
25
- *
26
- * This avoids all the complexity of Web Workers:
27
- * - No CSP workarounds (blob URLs, inline scripts)
28
- * - No bundler configuration (worker-plugin, import.meta.url)
29
- * - No message passing protocol
30
- * - Standard npm import — just works
31
- *
32
- * ── Interface ────────────────────────────────────────────────────────────
33
- * Same shape as createSlowLaneClient so text-predictor.ts needs zero changes.
34
- * The client exposes getContextVector() and getLmLogits() which are populated
35
- * asynchronously after each updateContext() call.
36
- */
37
- export interface LocalSlowLaneClientConfig {
38
- /**
39
- * Optional custom model registration for models not in web-llm's
40
- * built-in list. When provided, the model is appended to the app
41
- * config before engine creation.
42
- */
43
- customModelConfig?: {
44
- /** Context window size override (optional) */
45
- contextWindowSize?: number;
46
- /** HuggingFace URL to the model weights (e.g. "https://huggingface.co/HuggingFaceTB/smollm-135M-instruct-add-basics-q0f16-MLC") */
47
- model: string;
48
- /** URL to the compiled WASM library for this model architecture */
49
- modelLib: string;
50
- /** VRAM required in MB (optional, for resource planning) */
51
- vramRequiredMB?: number;
52
- };
53
- /** Debounce interval in ms before sending context for inference. */
54
- debounceMs?: number;
55
- /**
56
- * MLC model identifier.
57
- * Defaults to the built-in SmolLM2-135M-Instruct-q0f16-MLC.
58
- *
59
- * To use a custom HuggingFace model, provide both `modelId` and
60
- * `customModelConfig` with the model URL and WASM library URL.
61
- */
62
- modelId?: string;
63
- /** Callback fired when the engine fails to load/start. */
64
- onLoadError?: (error: LocalSlowLaneLoadError) => void;
65
- /** Callback fired when the engine successfully loads and is ready. */
66
- onLoadSuccess?: (info: LocalSlowLaneLoadSuccess) => void;
67
- /** Callback fired with status messages (model loading progress, etc.). */
68
- onStatus?: (message: string) => void;
69
- /** Callback fired when inference returns new results. */
70
- onUpdate?: (opts: {
71
- hasLmLogits: boolean;
72
- hasVector: boolean;
73
- textLength: number;
74
- }) => void;
75
- }
76
- export interface LocalSlowLaneClient {
77
- /** Clean up resources. */
78
- destroy: () => void;
79
- getContextVector: () => Float32Array | null;
80
- getLmLogits: () => Record<string, number> | null;
81
- /** Whether the model is loaded and ready for inference. */
82
- isReady: () => boolean;
83
- isWordBoundary: (text: string) => boolean;
84
- setContextVector: (vector: Float32Array | null) => void;
85
- setLmLogits: (logits: Record<string, number> | null) => void;
86
- updateContext: (text: string) => void;
87
- }
88
- /**
89
- * Why the local engine failed to load/start.
90
- *
91
- * The first three are user-machine limitations (WebGPU missing, no compatible
92
- * GPU adapter, GPU lacks the `shader-f16` feature the model needs);
93
- * `insufficient_memory` is hit when weights don't fit in VRAM. The rest cover
94
- * delivery/runtime failures unrelated to hardware.
95
- */
96
- export type LocalSlowLaneLoadErrorReason = 'webgpu_unavailable' | 'webgpu_no_adapter' | 'missing_shader_f16' | 'insufficient_memory' | 'model_download_failed' | 'module_load_failed' | 'init_failed';
97
- /** Snapshot of the machine's WebGPU support, used to explain hardware limits. */
98
- export interface WebGpuCapabilities {
99
- /** Whether `navigator.gpu.requestAdapter()` returned a usable adapter. */
100
- adapterAvailable?: boolean;
101
- /** GPU architecture reported by the adapter (e.g. "metal-3", "rdna2"). */
102
- architecture?: string;
103
- /** Whether `navigator.gpu` exists at all. */
104
- available: boolean;
105
- /** Largest single GPU buffer the adapter allows, in MB. */
106
- maxBufferSizeMB?: number;
107
- /** Largest storage-buffer binding the adapter allows, in MB. */
108
- maxStorageBufferBindingSizeMB?: number;
109
- /** Whether the adapter exposes the `shader-f16` feature the model requires. */
110
- shaderF16Supported?: boolean;
111
- /** GPU vendor reported by the adapter (e.g. "apple", "intel"). */
112
- vendor?: string;
113
- }
114
- export interface LocalSlowLaneLoadError {
115
- /** WebGPU support snapshot — explains hardware limitations behind the failure. */
116
- capabilities: WebGpuCapabilities;
117
- /** Semantic embedder loaded alongside the causal LM (loads atomically). */
118
- embeddingModelId: string;
119
- /** Canonical, controlled failure description (never raw error text). */
120
- message: string;
121
- /** Causal LM identifier that failed to load. */
122
- modelId: string;
123
- /** Coarse, privacy-safe failure category. */
124
- reason: LocalSlowLaneLoadErrorReason;
125
- }
126
- export interface LocalSlowLaneLoadSuccess {
127
- /** WebGPU support snapshot for the machine that loaded the model. */
128
- capabilities: WebGpuCapabilities;
129
- /** Semantic embedder loaded alongside the causal LM (loads atomically). */
130
- embeddingModelId: string;
131
- /** Model engine load time in ms (excludes the WebGPU capability probe). */
132
- loadDurationMs: number;
133
- /** Causal LM identifier that loaded. */
134
- modelId: string;
135
- }
136
- export declare const LOCAL_MLC_CAUSAL_MODEL_ID = "SmolLM2-135M-Instruct-q0f16-MLC";
137
- /**
138
- * MLC ID for the semantic embedder (Snowflake Arctic Embed S, batch=4 variant).
139
- *
140
- * The `-b4` suffix selects the prebuilt variant compiled for a max batch size of
141
- * 4 (≈239 MB VRAM) rather than `-b32` (≈1023 MB VRAM). Autocomplete embeds one
142
- * context at a time, so `-b4` is the right fit. This model IS in
143
- * `prebuiltAppConfig.model_list` of web-llm 0.2.82 — no `customModelConfig` needed.
144
- */
145
- export declare const LOCAL_MLC_EMBEDDING_MODEL_ID = "snowflake-arctic-embed-s-q0f32-MLC-b4";
146
- /**
147
- * Wrap raw context text with BERT special tokens before embedding.
148
- *
149
- * web-llm's `EmbeddingPipeline` does NOT auto-prepend `[CLS]` / append `[SEP]`
150
- * (the official MLC embeddings example wraps manually). The Python
151
- * `sentence_transformers` side that generated the word-vector bin adds these
152
- * inside `model.encode()`, so we must mirror it here for the runtime context
153
- * vector to land in the same region of Arctic's embedding space as the bin.
154
- *
155
- * No query prefix is applied: the semantic step is sentence-to-sentence (`s2s`)
156
- * similarity ("which words are conceptually similar to this context?"), not
157
- * sentence-to-passage (`s2p`) retrieval. Arctic's query prefix would misframe
158
- * the relationship. Encode both sides as passages. See implementation.md §4.3.
159
- */
160
- export declare const wrapForArctic: (text: string) => string;
161
- /**
162
- * BE-parity constants — must match `CausalLMEncoder` defaults in the Python
163
- * sidecar (`cc-smarts/python-sidecar/src/causal_lm_encoder.py`) and
164
- * `SlowLaneEngine` (`typeahead_context_encoding.py`) so local payloads behave
165
- * identically to the server-client setup.
166
- */
167
- export declare const BE_PARITY: {
168
- /** Final payload size cap (BE: `top_k_words`). */
169
- readonly TOP_K_WORDS: 2000;
170
- /** L2 (domain) words admitted unconditionally before pooling (BE: `reserved_l2_slots`). */
171
- readonly RESERVED_L2_SLOTS: 500;
172
- /** Log-space additive bias favouring L2 over L3 in the pool (BE: `l2_bias`). */
173
- readonly L2_BIAS: 1;
174
- /** Drop words below this probability from the final payload (BE: `> 0.00001`). */
175
- readonly MIN_PROB: 0.00001;
176
- /**
177
- * Word-level approximation of the BE causal LM token limit.
178
- *
179
- * BE: `CausalLMEncoder.max_context_tokens = 100` (BPE tokens, left-truncated).
180
- * FE: no tokenizer available, so we approximate with word count. English text
181
- * averages ~1.3–1.5 BPE tokens/word, meaning 100 words ≈ 130–150 tokens.
182
- * Using 100 words keeps the approximation simple and errs on the side of
183
- * sending slightly more context than the BE sees — acceptable for a PoC.
184
- */
185
- readonly MAX_CONTEXT_TOKENS: 100;
186
- /**
187
- * Word-level rolling window for the semantic embedder.
188
- *
189
- * BE: `SlowLaneEngine.max_context_words = 100` (applied in
190
- * `typeahead_context_encoding.py` before calling `SemanticEncoder.encode`).
191
- * Truncated identically here so the runtime Arctic vector lands in the same
192
- * region of the embedding space as the precomputed word-vector bin.
193
- */
194
- readonly MAX_CONTEXT_WORDS: 100;
195
- };
196
- /**
197
- * Convert a raw next-token logit vector into a whole-word probability payload,
198
- * faithfully porting the BE `CausalLMEncoder._get_top_k_probs`
199
- * (`cc-smarts/python-sidecar/src/causal_lm_encoder.py`).
200
- *
201
- * Steps: (1) numerically-stable masked softmax over only the token ids present
202
- * in the prefix-expansion map; (2) spread each token's probability to every
203
- * whole word sharing that first token, taking the max; (3) reserve the top L2
204
- * words unconditionally; (4) rank the remainder in a log-space pool with an
205
- * additive L2 bias; (5) emit raw probabilities for the survivors, lowercased
206
- * and trimmed at `MIN_PROB`.
207
- *
208
- * :params:
209
- * rawLogits: Full-vocabulary logits from the LM's single decode step
210
- * prefixMap: Map of first-token id to the words starting with that token
211
- * domainWords: Set of L2 (domain) words, for tier-aware ranking
212
- * :returns:
213
- * A record of lowercase word to probability — the BE `lm_logits` payload
214
- */
215
- export declare const computeBePayload: (rawLogits: Float32Array, prefixMap: Map<number, string[]>, domainWords: Set<string>,
216
- /**
217
- * Pre-derived token-ID array for the softmax mask. Defaults to the
218
- * module-level `prefixMapTokenIds` (zero allocation in production). Pass
219
- * `Array.from(prefixMap.keys())` in tests that supply a custom prefixMap so
220
- * the softmax mask stays consistent with the iteration in Step 2.
221
- */
222
- validTokenIds?: number[]) => Record<string, number>;
223
- /**
224
- * Create a local slow-lane client powered by MLC WebLLM.
225
- *
226
- * The engine is initialised lazily — model weights are downloaded (and cached
227
- * in IndexedDB) on first use. Subsequent page loads skip the download.
228
- *
229
- * Usage:
230
- * ```ts
231
- * const client = createLocalSlowLaneClient({ debounceMs: 300 });
232
- * // On word boundaries:
233
- * client.updateContext(docText);
234
- * // In scoring pipeline:
235
- * const vec = client.getContextVector();
236
- * const logits = client.getLmLogits();
237
- * // On plugin teardown:
238
- * client.destroy();
239
- * ```
240
- */
241
- export declare const createLocalSlowLaneClient: (config?: LocalSlowLaneClientConfig) => LocalSlowLaneClient;
@@ -1,43 +0,0 @@
1
- /**
2
- * Scoring Pipeline: Stage 1 (Semantic + Frequency), Grammar Filter, Stage 2 (LM Re-ranking).
3
- *
4
- * Operates synchronously on pre-loaded data. Each stage gracefully degrades
5
- * when its required data isn't available (cold → warm → full warm).
6
- */
7
- export interface ScoringCandidate {
8
- authorFreq: number;
9
- docFreq: number;
10
- sessionFreq: number;
11
- tenantFreq: number;
12
- word: string;
13
- }
14
- export interface ScoredCandidate {
15
- finalScore: number;
16
- freqScore: number;
17
- lmScore: number;
18
- semanticScore: number;
19
- word: string;
20
- }
21
- /** Metadata returned by the grammar filter for debug logging in the caller. */
22
- export interface GrammarFilterMeta {
23
- after: number;
24
- before: number;
25
- dropped: string[];
26
- prevTags: string[];
27
- prevWord: string;
28
- }
29
- export declare const STAGE1_WEIGHT = 0.35;
30
- export declare const STAGE2_WEIGHT = 0.65;
31
- export declare const MIN_STAGE1_SCORE = 0.35;
32
- export interface PipelineDebug {
33
- final: number;
34
- grammarRejected: string[];
35
- initial: number;
36
- stage1Rejected: string[];
37
- }
38
- export interface RankCandidatesResult {
39
- candidates: ScoredCandidate[];
40
- grammarMeta: GrammarFilterMeta | null;
41
- pipelineDebug: PipelineDebug;
42
- }
43
- export declare function rankCandidates(candidates: ScoringCandidate[], contextVector: Float32Array | null, getWordVector: (word: string) => Float32Array | null, lmLogits: Record<string, number> | null, maxTenantFreq: number, previousWord: string): RankCandidatesResult;
@@ -1,46 +0,0 @@
1
- /**
2
- * Slow Lane Client: Backend context encoding for autocomplete.
3
- *
4
- * Fires a BE request on word boundaries to encode document context.
5
- * Expects the typeahead-encodings API format:
6
- * Request: { text, session_id }
7
- * Response: { semantic_vector: number[], lm_logits: Record<string, number> }
8
- */
9
- /** Request payload for typeahead-encodings endpoint. */
10
- export interface TypeaheadEncodingsRequest {
11
- session_id: string;
12
- text: string;
13
- }
14
- /** Response from typeahead-encodings endpoint. */
15
- export interface TypeaheadEncodingsResponse {
16
- lm_logits: Record<string, number>;
17
- semantic_vector: number[];
18
- }
19
- /**
20
- * Check if text ends with a word boundary character (space or punctuation).
21
- */
22
- export declare const isWordBoundary: (text: string) => boolean;
23
- export interface SlowLaneClientConfig {
24
- baseUrl: string;
25
- debounceMs?: number;
26
- endpoint?: string;
27
- fetchFn?: typeof fetch;
28
- onUpdate?: (opts: {
29
- hasLmLogits: boolean;
30
- hasVector: boolean;
31
- textLength: number;
32
- }) => void;
33
- productKey?: string;
34
- sessionId?: string;
35
- }
36
- export declare const createSlowLaneClient: (config: SlowLaneClientConfig) => {
37
- getContextVector: () => Float32Array | null;
38
- getLmLogits: () => Record<string, number> | null;
39
- isWordBoundary: (text: string) => boolean;
40
- setContextVector: (vector: Float32Array | null) => void;
41
- setLmLogits: (logits: Record<string, number> | null) => void;
42
- updateContext: (text: string) => void;
43
- };
44
- export declare const setDefaultSlowLaneClient: (client: ReturnType<typeof createSlowLaneClient> | null) => void;
45
- export declare const getStoredContextVector: () => Float32Array | null;
46
- export declare const getStoredLmLogits: () => Record<string, number> | null;
@@ -1,91 +0,0 @@
1
- /**
2
- * Fast Lane Predictor: Local autocomplete using weighted trie + frequency + semantic scoring.
3
- *
4
- * Two prediction modes:
5
- * 1. Word boundary → bigram-based next-word suggestion (grammar-filtered)
6
- * 2. Mid-word (≥3 chars) → trie prefix search → scoring pipeline → top result
7
- *
8
- * Scoring is delegated to scoring-pipeline.ts which handles:
9
- * Stage 1 (semantic + frequency), grammar filter, Stage 2 (optional LM re-ranking).
10
- *
11
- * Context vector: average of word vectors from text before cursor (last N words).
12
- * Falls back to cold mode (freq-only) when vectors not yet loaded.
13
- *
14
- * Session personalization (L1): words the user types are incrementally boosted
15
- * via incrementSessionFreq(), called on word boundaries from the plugin.
16
- */
17
- export interface WeightedTerm {
18
- authorFreq: number;
19
- docFreq: number;
20
- freq: number;
21
- word: string;
22
- }
23
- export interface TenantVocabulary {
24
- terms: WeightedTerm[];
25
- }
26
- interface VectorStore {
27
- dim: number;
28
- float32: Float32Array;
29
- wordIndex: Record<string, number>;
30
- }
31
- /**
32
- * Loads the General English vocabulary.
33
- * expects a simple array of strings: ["about", "above", "actually", ...]
34
- */
35
- export declare const initL3Vocabulary: (l3Words: string[]) => void;
36
- /**
37
- * Get predictor status for debugging.
38
- * vectorsLoaded: true when semantic scoring is active
39
- * wordCount: number of words in vector store (0 if not loaded)
40
- */
41
- export declare const getPredictorStatus: () => {
42
- isInitialized: boolean;
43
- vectorsLoaded: boolean;
44
- vectorsLoadStarted: boolean;
45
- wordCount: number;
46
- };
47
- /**
48
- * Get details of the last prediction (for debugging).
49
- * Returns null if no prediction has run yet or debug was off.
50
- */
51
- export declare const getLastPredictionDebug: () => {
52
- contextWords: string[];
53
- currentWord: string;
54
- mode: "cold" | "warm";
55
- suggestion: string | null;
56
- textBefore: string;
57
- topCandidates: Array<{
58
- finalScore: number;
59
- freqScore: number;
60
- lmScore: number;
61
- semanticScore: number;
62
- word: string;
63
- }>;
64
- } | null;
65
- export declare const initVocabulary: (vocabulary: TenantVocabulary) => void;
66
- /**
67
- * Increment L1 session frequency for a single word.
68
- * Called from the plugin on word boundaries for efficient incremental boosting.
69
- */
70
- export declare const incrementSessionFreq: (word: string) => void;
71
- /**
72
- * Prime session frequencies from a document page string.
73
- *
74
- * Iterates through every token in `pageContent` and increments its session
75
- * frequency so that words already present on the page receive an L1 boost
76
- * before the user starts typing.
77
- *
78
- * Pass `undefined` (or omit the argument) to skip priming — useful when the
79
- * calling context does not yet have a page value available.
80
- */
81
- export declare const ingestDocumentPage: (pageContent: string | undefined) => void;
82
- export declare const predict: (textBefore: string) => string | null;
83
- export declare const loadVectorsAsync: (options?: {
84
- getBinaryUrl?: () => Promise<string>;
85
- isLocalLLM?: boolean;
86
- }) => Promise<void>;
87
- export declare const initVectors: (store: VectorStore) => void;
88
- export declare const loadDefaultVocabulary: (options?: {
89
- isLocalLLM?: boolean;
90
- }) => Promise<void>;
91
- export {};