@intentface/chat 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @intentface/chat
2
2
 
3
3
  Headless chat UI primitives for React — the behavior, state, and wire formats
4
- for building AI chat interfaces, with no styling of their own. Pair them with
5
- the shadcn-style styled layer from the [docs](https://intentface.dev/docs), or
6
- bring your own.
4
+ for building AI chat interfaces, with no styling of their own.
7
5
 
8
- This is the Base UI model applied to chat: **install the logic from npm, copy
9
- the look from the docs.**
6
+ This is the Base UI model applied to chat: **the package owns behavior, you own
7
+ every class.** There is no pre-styled `@intentface/chat` package. The demos on
8
+ each [docs](https://intentface.dev/docs) page show how the parts fit together
9
+ and are meant to be copied and restyled.
10
10
 
11
11
  ## Status
12
12
 
@@ -100,7 +100,7 @@ directive and import fine anywhere.
100
100
 
101
101
  ## Docs
102
102
 
103
- Full guides, live previews, and copy-paste styled source at
103
+ Full guides, live demos, and the API reference at
104
104
  [intentface.dev/docs](https://intentface.dev/docs).
105
105
 
106
106
  ## License
@@ -3,7 +3,9 @@
3
3
  // token, so a rendered message reconstructs the chip from its own text
4
4
  // alone — no sidecar metadata array. Presentation that's derivable at render
5
5
  // time (e.g. a per-prefix variant) is the renderer's job, not wire data.
6
- export const CHIP_REF_PATTERN = /\[([^\]]+)\]\(chip:([^:)]+):([^)?]+)(?:\?([^)]*))?\)/g;
6
+ // Every class excludes "[" so no group can consume past the next one — without
7
+ // that this backtracks quadratically, and message text is untrusted.
8
+ export const CHIP_REF_PATTERN = /\[([^[\]]+)\]\(chip:([^[:)]+):([^[)?]+)(?:\?([^[)]*))?\)/g;
7
9
  export const escapeMarkdownLink = (input) => input.replace(/[[\]()\\]/g, (match) => `\\${match}`);
8
10
  // Tolerate legacy/un-encoded values — a stray "%" would otherwise throw and
9
11
  // take the whole message render down with it.
@@ -2,6 +2,11 @@
2
2
  // active-token state shape and the pure scan that derives it from the text
3
3
  // around the caret. Sticky range tracking and dismissal memory live one layer
4
4
  // up, in trigger-tracker.ts.
5
+ // ---------------------------------------------------------------------------
6
+ // Types & constants
7
+ // ---------------------------------------------------------------------------
8
+ // Non-global: `.test` on a global regex advances lastIndex between calls.
9
+ const WHITESPACE = /\s/;
5
10
  export const CLOSED_COMMAND_STATE = {
6
11
  isOpen: false,
7
12
  trigger: null,
@@ -34,16 +39,20 @@ export const detectActivePrefix = (args) => {
34
39
  // taken from both sides of the caret so it stays whole as the caret moves
35
40
  // within it. Text chars map 1:1 to positions and an atomic chip can never
36
41
  // sit inside a run, so the run length is the position delta on each side.
37
- const leftRun = textBeforeCursor.match(/\S*$/)?.[0] ?? "";
42
+ // Scanned backwards, not /\S*$/ that backtracks quadratically when the
43
+ // text ends in whitespace, on a path that runs per keystroke.
44
+ let runStart = textBeforeCursor.length;
45
+ while (runStart > 0 && !WHITESPACE.test(textBeforeCursor[runStart - 1]))
46
+ runStart--;
47
+ const leftRun = textBeforeCursor.slice(runStart);
48
+ // Start-anchored, so this one cannot backtrack.
38
49
  const rightRun = textAfterCursor.match(/^\S*/)?.[0] ?? "";
39
50
  const runText = leftRun + rightRun;
40
51
  if (!runText.startsWith(entry.prefix))
41
52
  continue;
42
53
  // The prefix must sit at a word boundary: line start or after whitespace.
43
- const charBeforeRun = textBeforeCursor
44
- .slice(0, textBeforeCursor.length - leftRun.length)
45
- .at(-1);
46
- if (charBeforeRun !== undefined && !/\s/.test(charBeforeRun))
54
+ const charBeforeRun = runStart > 0 ? textBeforeCursor[runStart - 1] : undefined;
55
+ if (charBeforeRun !== undefined && !WHITESPACE.test(charBeforeRun))
47
56
  continue;
48
57
  return {
49
58
  isOpen: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentface/chat",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Headless chat UI primitives — unstyled compound components, hooks, and wire formats for building AI chat interfaces.",
5
5
  "license": "MIT",
6
6
  "type": "module",