@bitbaum/chatkit 0.14.1 → 0.14.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.
package/README.md CHANGED
@@ -163,12 +163,21 @@ sideways with a shadow on the edge that has more. The stack figure's tiles
163
163
  wrap too: three across becomes two and one, or one under the other, rather
164
164
  than cut "Manufacture".
165
165
 
166
- `test/browser.mjs` measures it rather than reading the CSS: every word of every
167
- answer is laid out in a `Range` at 320, 390 and 1280px and must sit on one line
166
+ `test/browser.mjs` measures it rather than reading the CSS: every word on the
167
+ page is laid out in a `Range` at 320, 390 and 1280px and must sit on one line
168
168
  (a word beside a hyphen, slash or dot, and anything over 24 characters, is
169
169
  exempt: a URL has to break somewhere). The demo thread carries the table from
170
170
  the screenshot, so the check fails on the old stylesheet.
171
171
 
172
+ The check is exported so any product can run it against its own pages, from a
173
+ Playwright script (never from app code):
174
+
175
+ ```js
176
+ import { BROKEN_WORDS_SOURCE } from "@bitbaum/chatkit/audit";
177
+ const broken = await page.evaluate(`(${BROKEN_WORDS_SOURCE})(document.body)`);
178
+ if (broken.length) throw new Error(`words cut across lines: ${broken}`);
179
+ ```
180
+
172
181
  ### The feedback loop (0.14)
173
182
 
174
183
  Two halves, one shape, so a bad answer is found by the product, not by the
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Layout audits for a real browser — the part no unit test can see.
3
+ *
4
+ * Import this from a Playwright script, never from app code. `findBrokenWords`
5
+ * runs INSIDE the page: it is self-contained on purpose (no imports, no
6
+ * closures), so its source can be handed to `page.evaluate`:
7
+ *
8
+ * import { BROKEN_WORDS_SOURCE } from "@bitbaum/chatkit/audit";
9
+ * const broken = await page.evaluate(`(${BROKEN_WORDS_SOURCE})(document.body)`);
10
+ * if (broken.length) throw new Error(`words cut across lines: ${broken}`);
11
+ *
12
+ * Why it exists: Substrata's answer table at 390px read "Te / rm" and
13
+ * "Di / re / ct / or / y" (2026-10-22). Reading the CSS finds none of it; only
14
+ * laying the words out does.
15
+ */
16
+ /**
17
+ * Every word under `root` that is drawn across two lines. A word may wrap onto
18
+ * the next line; it may never be cut in two. Measured, not inferred: each word
19
+ * is laid out in a `Range` and must sit on one line.
20
+ *
21
+ * Exempt, because a break there is legal: a word beside a hyphen, slash, dot,
22
+ * at-sign, colon or underscore, and anything over `maxWord` characters (a URL
23
+ * or an identifier has to break somewhere). Hidden text has no boxes and is
24
+ * skipped.
25
+ */
26
+ export declare function findBrokenWords(root?: ParentNode, options?: {
27
+ maxWord?: number;
28
+ }): string[];
29
+ /** `findBrokenWords` as source text, for `page.evaluate`. */
30
+ export declare const BROKEN_WORDS_SOURCE: string;
31
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,UAAqB,EAC3B,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,MAAM,EAAE,CA0BV;AAED,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,EAAE,MAAmC,CAAC"}
package/dist/audit.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Layout audits for a real browser — the part no unit test can see.
3
+ *
4
+ * Import this from a Playwright script, never from app code. `findBrokenWords`
5
+ * runs INSIDE the page: it is self-contained on purpose (no imports, no
6
+ * closures), so its source can be handed to `page.evaluate`:
7
+ *
8
+ * import { BROKEN_WORDS_SOURCE } from "@bitbaum/chatkit/audit";
9
+ * const broken = await page.evaluate(`(${BROKEN_WORDS_SOURCE})(document.body)`);
10
+ * if (broken.length) throw new Error(`words cut across lines: ${broken}`);
11
+ *
12
+ * Why it exists: Substrata's answer table at 390px read "Te / rm" and
13
+ * "Di / re / ct / or / y" (2026-10-22). Reading the CSS finds none of it; only
14
+ * laying the words out does.
15
+ */
16
+ /**
17
+ * Every word under `root` that is drawn across two lines. A word may wrap onto
18
+ * the next line; it may never be cut in two. Measured, not inferred: each word
19
+ * is laid out in a `Range` and must sit on one line.
20
+ *
21
+ * Exempt, because a break there is legal: a word beside a hyphen, slash, dot,
22
+ * at-sign, colon or underscore, and anything over `maxWord` characters (a URL
23
+ * or an identifier has to break somewhere). Hidden text has no boxes and is
24
+ * skipped.
25
+ */
26
+ export function findBrokenWords(root = document, options = {}) {
27
+ const maxWord = options.maxWord ?? 24;
28
+ const found = [];
29
+ const near = /[-‐‑–—/_.@:]/;
30
+ const walker = document.createTreeWalker(root instanceof Document ? root.body : root, NodeFilter.SHOW_TEXT);
31
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
32
+ const text = n.nodeValue ?? "";
33
+ const re = /[\p{L}\p{N}']+/gu;
34
+ for (let m = re.exec(text); m; m = re.exec(text)) {
35
+ const word = m[0];
36
+ if (word.length > maxWord)
37
+ continue;
38
+ if (near.test(text[m.index - 1] ?? "") || near.test(text[m.index + word.length] ?? ""))
39
+ continue;
40
+ const range = document.createRange();
41
+ range.setStart(n, m.index);
42
+ range.setEnd(n, m.index + word.length);
43
+ const tops = Array.from(range.getClientRects())
44
+ .filter((r) => r.width > 0)
45
+ .map((r) => Math.round(r.top));
46
+ if (tops.length > 1 && Math.max(...tops) - Math.min(...tops) > 6)
47
+ found.push(word);
48
+ }
49
+ }
50
+ return found;
51
+ }
52
+ /** `findBrokenWords` as source text, for `page.evaluate`. */
53
+ export const BROKEN_WORDS_SOURCE = findBrokenWords.toString();
54
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAmB,QAAQ,EAC3B,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,cAAc,CAAC;IAC5B,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CACtC,IAAI,YAAY,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,IAAa,EACrD,UAAU,CAAC,SAAS,CACrB,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;QAC/B,MAAM,EAAE,GAAG,kBAAkB,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAClB,IAAI,IAAI,CAAC,MAAM,GAAG,OAAO;gBAAE,SAAS;YACpC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACpF,SAAS;YACX,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;YACrC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;YAC3B,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;iBAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;iBAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAW,eAAe,CAAC,QAAQ,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbaum/chatkit",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "One chat for every product: a composer with a microphone that always works, a thread that does not fight the reader, messages with copy and retry, and LLM markdown \u2014 the fleet's chat standard as a package.",
5
5
  "license": "MIT",
6
6
  "author": "Cato",
@@ -51,6 +51,10 @@
51
51
  "types": "./dist/react/index.d.ts",
52
52
  "default": "./dist/react/index.js"
53
53
  },
54
+ "./audit": {
55
+ "types": "./dist/audit.d.ts",
56
+ "default": "./dist/audit.js"
57
+ },
54
58
  "./attach": {
55
59
  "types": "./dist/react/attach.d.ts",
56
60
  "default": "./dist/react/attach.js"