@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 +11 -2
- package/dist/audit.d.ts +31 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +54 -0
- package/dist/audit.js.map +1 -0
- package/package.json +5 -1
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
|
|
167
|
-
|
|
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
|
package/dist/audit.d.ts
ADDED
|
@@ -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.
|
|
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"
|