@remnic/core 9.61.1 → 9.62.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/dist/access-admin-ops-surface.js +11 -11
- package/dist/access-authorization-probe.js +12 -12
- package/dist/access-boundary.js +11 -11
- package/dist/access-cli.js +43 -43
- package/dist/access-extraction-force-flush.js +11 -11
- package/dist/access-http-query.js +11 -11
- package/dist/access-http.js +20 -20
- package/dist/access-identity-continuity-surface.js +11 -11
- package/dist/access-lcm-surface.js +11 -11
- package/dist/access-mcp.js +16 -16
- package/dist/access-namespace-preflight.js +11 -11
- package/dist/access-observe-write-surface.js +11 -11
- package/dist/access-operations-batch.js +12 -12
- package/dist/access-operations.js +15 -15
- package/dist/access-recall-concurrency.js +11 -11
- package/dist/access-recall-response.js +11 -11
- package/dist/access-recall-surface.js +11 -11
- package/dist/access-service.js +11 -11
- package/dist/access-surface-catalog.js +13 -13
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.js +3 -3
- package/dist/briefing.js +4 -4
- package/dist/causal-consolidation.js +5 -5
- package/dist/{chunk-KAB7TZN3.js → chunk-3G6YU5YD.js} +2 -2
- package/dist/{chunk-XD4VGLDL.js → chunk-3XG4QEA2.js} +35 -31
- package/dist/chunk-3XG4QEA2.js.map +1 -0
- package/dist/{chunk-HEEEMJ26.js → chunk-4E67XCT2.js} +3 -3
- package/dist/{chunk-H32ANIFP.js → chunk-4G45LJJP.js} +2 -2
- package/dist/{chunk-4XAVQLBR.js → chunk-4Y2RHVCV.js} +2 -2
- package/dist/{chunk-S3B3UIQ3.js → chunk-67MKNKJQ.js} +2 -2
- package/dist/{chunk-PA5OPZX2.js → chunk-75U2FR7U.js} +2 -2
- package/dist/{chunk-I67HKRBF.js → chunk-7JAXC526.js} +2 -2
- package/dist/{chunk-3GPBFDXH.js → chunk-7PVTOZR4.js} +2 -2
- package/dist/{chunk-DSA47WBN.js → chunk-BHO3IVAN.js} +2 -2
- package/dist/{chunk-MYZ4NA43.js → chunk-DYDACAK2.js} +2 -2
- package/dist/{chunk-4JGSG7QN.js → chunk-ETCVSZLG.js} +2 -2
- package/dist/{chunk-EIQFIJDM.js → chunk-EU74U4GT.js} +2 -2
- package/dist/{chunk-L33N3BCA.js → chunk-GZRDOVQN.js} +2 -2
- package/dist/{chunk-AWRJBAIH.js → chunk-HKYNBEL4.js} +6 -6
- package/dist/{chunk-MJ4UGUR6.js → chunk-IIRK5V5Z.js} +2 -2
- package/dist/{chunk-2RIDW23Y.js → chunk-IXJ5U6ZL.js} +8 -8
- package/dist/{chunk-QRKYABS6.js → chunk-JDJOAL4B.js} +2 -2
- package/dist/{chunk-GN7HO2LA.js → chunk-JF6YSHJM.js} +7 -7
- package/dist/{chunk-D5HDJWNN.js → chunk-JLMH5YMU.js} +3 -3
- package/dist/{chunk-FW73HN33.js → chunk-K4WIUTNH.js} +2 -2
- package/dist/{chunk-TE4MKFT2.js → chunk-KCFZOSR6.js} +4 -4
- package/dist/{chunk-VWDTV6SH.js → chunk-KYI3CM42.js} +2 -2
- package/dist/{chunk-CIA6ZUBA.js → chunk-LAM6UDFV.js} +2 -2
- package/dist/{chunk-W4Q54HM3.js → chunk-LLLY5IBK.js} +2 -2
- package/dist/{chunk-CM4SZZBN.js → chunk-MJFDJTDC.js} +2 -2
- package/dist/{chunk-VOELBUS4.js → chunk-NXH34IPL.js} +2 -2
- package/dist/{chunk-MU3TPZOV.js → chunk-PBH2Z2NY.js} +2 -2
- package/dist/chunk-PJKTFXE2.js +119 -0
- package/dist/chunk-PJKTFXE2.js.map +1 -0
- package/dist/{chunk-G3YBWFUC.js → chunk-QN7XQEIT.js} +34 -17
- package/dist/chunk-QN7XQEIT.js.map +1 -0
- package/dist/{chunk-HQBVQICL.js → chunk-QNMNNFKD.js} +2 -2
- package/dist/{chunk-EGW4O32Z.js → chunk-QRA3XVJX.js} +7 -4
- package/dist/chunk-QRA3XVJX.js.map +1 -0
- package/dist/{chunk-L4DLLKPN.js → chunk-SZ4HFZKB.js} +2 -2
- package/dist/{chunk-N4OYPGZS.js → chunk-TLUSSS2F.js} +2 -2
- package/dist/{chunk-N4EPIKSY.js → chunk-U27G4E5G.js} +3 -3
- package/dist/{chunk-W3Y6FYRK.js → chunk-UIRU3MIJ.js} +2 -2
- package/dist/{chunk-URN7XWGC.js → chunk-WANOCU3K.js} +2 -2
- package/dist/{chunk-XDK6ZFLL.js → chunk-WTMG5GAK.js} +2 -2
- package/dist/{chunk-C3R732UY.js → chunk-XIABRKF6.js} +37 -32
- package/dist/chunk-XIABRKF6.js.map +1 -0
- package/dist/{chunk-D4OCJSLV.js → chunk-YDPAE2RD.js} +2 -2
- package/dist/{chunk-FQSDU634.js → chunk-YG56A6XY.js} +2 -2
- package/dist/{chunk-XBR5BAKW.js → chunk-YSUSUZR4.js} +3 -3
- package/dist/{chunk-YUIBXMZY.js → chunk-ZJKLGS76.js} +2 -2
- package/dist/cli.js +32 -32
- package/dist/compounding/engine.js +4 -4
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.js +4 -4
- package/dist/connectors/index.js +4 -4
- package/dist/convergence-refresh.js +6 -6
- package/dist/corpus-watermark.js +6 -6
- package/dist/entity-retrieval.js +4 -4
- package/dist/external-wiki-access.js +12 -12
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.js +7 -7
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +56 -43
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/namespaces/migrate.js +5 -5
- package/dist/namespaces/storage.js +4 -4
- package/dist/operator-doctor-corpus.js +7 -7
- package/dist/operator-toolkit.js +12 -12
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/orchestrator.js +43 -43
- package/dist/provenance.js +2 -2
- package/dist/resume-bundles.js +4 -4
- package/dist/semantic-consolidation.js +5 -5
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.js +4 -4
- package/dist/source-agent-qualifier.js +6 -6
- package/dist/storage.js +3 -3
- package/dist/support-passport/index.js +14 -14
- package/dist/verified-recall.js +4 -4
- package/dist/whitespace.d.ts +26 -1
- package/dist/whitespace.js +11 -3
- package/package.json +2 -2
- package/src/cli.ts +14 -13
- package/src/entity-retrieval.ts +4 -7
- package/src/harmonic-construction.test.ts +187 -2
- package/src/harmonic-retrieval.ts +54 -32
- package/src/index.ts +3 -4
- package/src/orchestration/recall-section-coordinator.ts +20 -14
- package/src/whitespace.test.ts +178 -0
- package/src/whitespace.ts +119 -0
- package/dist/chunk-6MKAMLQL.js +0 -16
- package/dist/chunk-6MKAMLQL.js.map +0 -1
- package/dist/chunk-C3R732UY.js.map +0 -1
- package/dist/chunk-EGW4O32Z.js.map +0 -1
- package/dist/chunk-G3YBWFUC.js.map +0 -1
- package/dist/chunk-XD4VGLDL.js.map +0 -1
- /package/dist/{chunk-KAB7TZN3.js.map → chunk-3G6YU5YD.js.map} +0 -0
- /package/dist/{chunk-HEEEMJ26.js.map → chunk-4E67XCT2.js.map} +0 -0
- /package/dist/{chunk-H32ANIFP.js.map → chunk-4G45LJJP.js.map} +0 -0
- /package/dist/{chunk-4XAVQLBR.js.map → chunk-4Y2RHVCV.js.map} +0 -0
- /package/dist/{chunk-S3B3UIQ3.js.map → chunk-67MKNKJQ.js.map} +0 -0
- /package/dist/{chunk-PA5OPZX2.js.map → chunk-75U2FR7U.js.map} +0 -0
- /package/dist/{chunk-I67HKRBF.js.map → chunk-7JAXC526.js.map} +0 -0
- /package/dist/{chunk-3GPBFDXH.js.map → chunk-7PVTOZR4.js.map} +0 -0
- /package/dist/{chunk-DSA47WBN.js.map → chunk-BHO3IVAN.js.map} +0 -0
- /package/dist/{chunk-MYZ4NA43.js.map → chunk-DYDACAK2.js.map} +0 -0
- /package/dist/{chunk-4JGSG7QN.js.map → chunk-ETCVSZLG.js.map} +0 -0
- /package/dist/{chunk-EIQFIJDM.js.map → chunk-EU74U4GT.js.map} +0 -0
- /package/dist/{chunk-L33N3BCA.js.map → chunk-GZRDOVQN.js.map} +0 -0
- /package/dist/{chunk-AWRJBAIH.js.map → chunk-HKYNBEL4.js.map} +0 -0
- /package/dist/{chunk-MJ4UGUR6.js.map → chunk-IIRK5V5Z.js.map} +0 -0
- /package/dist/{chunk-2RIDW23Y.js.map → chunk-IXJ5U6ZL.js.map} +0 -0
- /package/dist/{chunk-QRKYABS6.js.map → chunk-JDJOAL4B.js.map} +0 -0
- /package/dist/{chunk-GN7HO2LA.js.map → chunk-JF6YSHJM.js.map} +0 -0
- /package/dist/{chunk-D5HDJWNN.js.map → chunk-JLMH5YMU.js.map} +0 -0
- /package/dist/{chunk-FW73HN33.js.map → chunk-K4WIUTNH.js.map} +0 -0
- /package/dist/{chunk-TE4MKFT2.js.map → chunk-KCFZOSR6.js.map} +0 -0
- /package/dist/{chunk-VWDTV6SH.js.map → chunk-KYI3CM42.js.map} +0 -0
- /package/dist/{chunk-CIA6ZUBA.js.map → chunk-LAM6UDFV.js.map} +0 -0
- /package/dist/{chunk-W4Q54HM3.js.map → chunk-LLLY5IBK.js.map} +0 -0
- /package/dist/{chunk-CM4SZZBN.js.map → chunk-MJFDJTDC.js.map} +0 -0
- /package/dist/{chunk-VOELBUS4.js.map → chunk-NXH34IPL.js.map} +0 -0
- /package/dist/{chunk-MU3TPZOV.js.map → chunk-PBH2Z2NY.js.map} +0 -0
- /package/dist/{chunk-HQBVQICL.js.map → chunk-QNMNNFKD.js.map} +0 -0
- /package/dist/{chunk-L4DLLKPN.js.map → chunk-SZ4HFZKB.js.map} +0 -0
- /package/dist/{chunk-N4OYPGZS.js.map → chunk-TLUSSS2F.js.map} +0 -0
- /package/dist/{chunk-N4EPIKSY.js.map → chunk-U27G4E5G.js.map} +0 -0
- /package/dist/{chunk-W3Y6FYRK.js.map → chunk-UIRU3MIJ.js.map} +0 -0
- /package/dist/{chunk-URN7XWGC.js.map → chunk-WANOCU3K.js.map} +0 -0
- /package/dist/{chunk-XDK6ZFLL.js.map → chunk-WTMG5GAK.js.map} +0 -0
- /package/dist/{chunk-D4OCJSLV.js.map → chunk-YDPAE2RD.js.map} +0 -0
- /package/dist/{chunk-FQSDU634.js.map → chunk-YG56A6XY.js.map} +0 -0
- /package/dist/{chunk-XBR5BAKW.js.map → chunk-YSUSUZR4.js.map} +0 -0
- /package/dist/{chunk-YUIBXMZY.js.map → chunk-ZJKLGS76.js.map} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
collapseWhitespace,
|
|
6
|
+
displayWidth,
|
|
7
|
+
graphemeUnits,
|
|
8
|
+
padEndDisplay,
|
|
9
|
+
truncateCodePointSafe,
|
|
10
|
+
truncateGraphemeSafe,
|
|
11
|
+
} from "./whitespace.js";
|
|
12
|
+
|
|
13
|
+
function hasLoneSurrogate(value: string): boolean {
|
|
14
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
15
|
+
const code = value.charCodeAt(index);
|
|
16
|
+
const isHigh = code >= 0xd800 && code <= 0xdbff;
|
|
17
|
+
const isLow = code >= 0xdc00 && code <= 0xdfff;
|
|
18
|
+
if (isHigh) {
|
|
19
|
+
const next = value.charCodeAt(index + 1);
|
|
20
|
+
if (next < 0xdc00 || next > 0xdfff) return true;
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (isLow) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isGraphemePrefix(prefix: string, value: string): boolean {
|
|
30
|
+
let accumulated = "";
|
|
31
|
+
for (const unit of graphemeUnits(value)) {
|
|
32
|
+
if (accumulated === prefix) return true;
|
|
33
|
+
accumulated += unit;
|
|
34
|
+
}
|
|
35
|
+
return accumulated === prefix;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Deterministic pseudo-random source so failures replay exactly.
|
|
39
|
+
function lcg(seed: number): () => number {
|
|
40
|
+
let state = seed >>> 0;
|
|
41
|
+
return () => {
|
|
42
|
+
state = (state * 1664525 + 1013904223) >>> 0;
|
|
43
|
+
return state / 0x1_0000_0000;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Every entry is one or more whole grapheme clusters covering the cases named
|
|
48
|
+
// in issue #2195: astral emoji, ZWJ sequences, skin-tone modifiers,
|
|
49
|
+
// regional-indicator flags, Hangul (precomposed and jamo), combining marks,
|
|
50
|
+
// and astral kanji.
|
|
51
|
+
const GRAPHEME_PIECES = [
|
|
52
|
+
"a",
|
|
53
|
+
"b",
|
|
54
|
+
" ",
|
|
55
|
+
"7",
|
|
56
|
+
"😀",
|
|
57
|
+
"👨👩👧👦",
|
|
58
|
+
"👍🏽",
|
|
59
|
+
"🇯🇵",
|
|
60
|
+
"🇺🇸",
|
|
61
|
+
"🇰🇷",
|
|
62
|
+
"한",
|
|
63
|
+
"한",
|
|
64
|
+
"e\u0301",
|
|
65
|
+
"k\u0331\u0301",
|
|
66
|
+
"क़",
|
|
67
|
+
"𠮷",
|
|
68
|
+
"日",
|
|
69
|
+
"本",
|
|
70
|
+
"語",
|
|
71
|
+
] as const;
|
|
72
|
+
|
|
73
|
+
test("truncateGraphemeSafe: property - valid Unicode, grapheme-aligned, within budget", () => {
|
|
74
|
+
const random = lcg(20260816);
|
|
75
|
+
for (let iteration = 0; iteration < 500; iteration += 1) {
|
|
76
|
+
let input = "";
|
|
77
|
+
const pieceCount = 1 + Math.floor(random() * 12);
|
|
78
|
+
for (let piece = 0; piece < pieceCount; piece += 1) {
|
|
79
|
+
input += GRAPHEME_PIECES[Math.floor(random() * GRAPHEME_PIECES.length)]!;
|
|
80
|
+
}
|
|
81
|
+
const maxChars = Math.floor(random() * (input.length + 2));
|
|
82
|
+
const output = truncateGraphemeSafe(input, maxChars);
|
|
83
|
+
assert.ok(
|
|
84
|
+
output.length <= maxChars,
|
|
85
|
+
`budget exceeded for ${JSON.stringify(input)} @ ${maxChars}: ${JSON.stringify(output)}`,
|
|
86
|
+
);
|
|
87
|
+
assert.ok(
|
|
88
|
+
!hasLoneSurrogate(output),
|
|
89
|
+
`lone surrogate for ${JSON.stringify(input)} @ ${maxChars}: ${JSON.stringify(output)}`,
|
|
90
|
+
);
|
|
91
|
+
assert.ok(
|
|
92
|
+
isGraphemePrefix(output, input),
|
|
93
|
+
`split grapheme for ${JSON.stringify(input)} @ ${maxChars}: ${JSON.stringify(output)}`,
|
|
94
|
+
);
|
|
95
|
+
if (input.length <= maxChars) {
|
|
96
|
+
assert.equal(output, input, `fits must pass through @ ${maxChars}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("truncateGraphemeSafe: emoji, ZWJ, flags, jamo, and combining marks stay whole", () => {
|
|
102
|
+
assert.equal(truncateGraphemeSafe("hello world", 5), "hello");
|
|
103
|
+
assert.equal(truncateGraphemeSafe("hello", 50), "hello");
|
|
104
|
+
assert.equal(truncateGraphemeSafe("hello", 0), "");
|
|
105
|
+
assert.equal(truncateGraphemeSafe("👍👍", 2), "👍");
|
|
106
|
+
assert.equal(truncateGraphemeSafe("👍", 1), "", "a split emoji is dropped, never half-cut");
|
|
107
|
+
assert.equal(truncateGraphemeSafe("a👍b", 2), "a");
|
|
108
|
+
assert.equal(truncateGraphemeSafe("🇯🇵🇺🇸", 4), "🇯🇵");
|
|
109
|
+
assert.equal(truncateGraphemeSafe("🇯🇵🇺🇸", 3), "", "a split flag is dropped, never half-cut");
|
|
110
|
+
const family = "👨👩👧👦";
|
|
111
|
+
assert.equal(truncateGraphemeSafe(`${family}!`, family.length + 1), `${family}!`);
|
|
112
|
+
assert.equal(truncateGraphemeSafe(`${family}!`, family.length), family);
|
|
113
|
+
assert.equal(truncateGraphemeSafe(family, family.length - 1), "", "ZWJ sequence is atomic");
|
|
114
|
+
assert.equal(truncateGraphemeSafe("한글", 3), "한", "jamo run is one cluster");
|
|
115
|
+
assert.equal(truncateGraphemeSafe("e\u0301x", 2), "e\u0301", "base plus mark is one cluster");
|
|
116
|
+
assert.equal(truncateGraphemeSafe("e\u0301x", 1), "");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("truncateCodePointSafe and collapseWhitespace keep prior behavior", () => {
|
|
120
|
+
assert.equal(collapseWhitespace(" a \n b "), "a b");
|
|
121
|
+
assert.equal(truncateCodePointSafe("abcd", 2), "ab");
|
|
122
|
+
assert.equal(truncateCodePointSafe("ab", 9), "ab");
|
|
123
|
+
assert.equal(truncateCodePointSafe("abc", 0), "");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("displayWidth: CJK wide, fullwidth, halfwidth, marks, and emoji", () => {
|
|
127
|
+
assert.equal(displayWidth(""), 0);
|
|
128
|
+
assert.equal(displayWidth("abc"), 3);
|
|
129
|
+
assert.equal(displayWidth("日本語"), 6);
|
|
130
|
+
assert.equal(displayWidth("ハングル"), 8);
|
|
131
|
+
assert.equal(displayWidth("한국어"), 6);
|
|
132
|
+
assert.equal(displayWidth("ア"), 1, "halfwidth katakana is narrow");
|
|
133
|
+
assert.equal(displayWidth("full"), 8, "fullwidth ASCII is wide");
|
|
134
|
+
assert.equal(displayWidth("e\u0301"), 1, "combining mark is zero width");
|
|
135
|
+
assert.equal(displayWidth("😀"), 2);
|
|
136
|
+
assert.equal(displayWidth("中日 memo"), 9, "2+2+1+4");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("displayWidth: emoji sequences measure as one two-cell grapheme", () => {
|
|
140
|
+
assert.equal(displayWidth("👨👩👧👦"), 2, "ZWJ family emoji is one cluster");
|
|
141
|
+
assert.equal(displayWidth("🇯🇵"), 2, "regional-indicator flag is one cluster");
|
|
142
|
+
assert.equal(displayWidth("👍🏽"), 2, "skin-tone modifier joins the cluster");
|
|
143
|
+
assert.equal(displayWidth("❤️"), 2, "VS16 requests emoji presentation");
|
|
144
|
+
assert.equal(displayWidth("1️⃣"), 2, "keycap sequence is one cluster");
|
|
145
|
+
assert.equal(displayWidth("👨👩👧👦 文"), 5, "clusters compose: 2 + 1 + 2");
|
|
146
|
+
assert.equal(
|
|
147
|
+
padEndDisplay("👨👩👧👦", 6),
|
|
148
|
+
"👨👩👧👦" + " ".repeat(4),
|
|
149
|
+
"emoji rows pad by display width, not code units",
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("padEndDisplay: CJK rows align to one computed width", () => {
|
|
154
|
+
assert.equal(padEndDisplay("abc", 6), "abc ");
|
|
155
|
+
assert.equal(padEndDisplay("abc", 2), "abc", "narrow target never truncates");
|
|
156
|
+
assert.equal(padEndDisplay("日本", 10), "日本" + " ".repeat(6));
|
|
157
|
+
const rows = ["機械学習", "nlp", "한국어", "x"];
|
|
158
|
+
const padded = rows.map((row) => padEndDisplay(row, 12));
|
|
159
|
+
for (const row of padded) {
|
|
160
|
+
assert.equal(displayWidth(row), 12);
|
|
161
|
+
}
|
|
162
|
+
assert.deepEqual(padded, [
|
|
163
|
+
"機械学習 ",
|
|
164
|
+
"nlp ",
|
|
165
|
+
"한국어 ",
|
|
166
|
+
"x ",
|
|
167
|
+
]);
|
|
168
|
+
// Snapshot-style: the CLI topics table shape keeps its score column aligned
|
|
169
|
+
// for CJK rows, which plain padEnd misaligns.
|
|
170
|
+
const table = rows.slice(0, 3).map((term) => ` ${padEndDisplay(term, 12)} 0.123`);
|
|
171
|
+
assert.deepEqual(table, [
|
|
172
|
+
" 機械学習 0.123",
|
|
173
|
+
" nlp 0.123",
|
|
174
|
+
" 한국어 0.123",
|
|
175
|
+
]);
|
|
176
|
+
const tableWidth = displayWidth(table[0]!);
|
|
177
|
+
assert.ok(table.every((line) => displayWidth(line) === tableWidth));
|
|
178
|
+
});
|
package/src/whitespace.ts
CHANGED
|
@@ -8,3 +8,122 @@ export function truncateCodePointSafe(value: string, maxChars: number): string {
|
|
|
8
8
|
if (glyphs.length <= maxChars) return value;
|
|
9
9
|
return glyphs.slice(0, Math.max(1, maxChars)).join("").trimEnd();
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
let graphemeSegmenter: Intl.Segmenter | null | undefined;
|
|
13
|
+
|
|
14
|
+
function getGraphemeSegmenter(): Intl.Segmenter | null {
|
|
15
|
+
if (graphemeSegmenter === undefined) {
|
|
16
|
+
graphemeSegmenter =
|
|
17
|
+
typeof Intl !== "undefined" && typeof Intl.Segmenter === "function"
|
|
18
|
+
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
19
|
+
: null;
|
|
20
|
+
}
|
|
21
|
+
return graphemeSegmenter;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Longest prefix of `value` whose UTF-16 length stays within `maxChars`
|
|
26
|
+
* without splitting a surrogate pair or a grapheme cluster (ZWJ emoji
|
|
27
|
+
* sequences, flags, Hangul jamo runs, combining marks). A cluster wider than
|
|
28
|
+
* the budget is dropped whole rather than split. Falls back to code-point
|
|
29
|
+
* boundaries when Intl.Segmenter is unavailable.
|
|
30
|
+
*/
|
|
31
|
+
export function truncateGraphemeSafe(value: string, maxChars: number): string {
|
|
32
|
+
if (maxChars <= 0) return "";
|
|
33
|
+
if (value.length <= maxChars) return value;
|
|
34
|
+
let truncated = "";
|
|
35
|
+
for (const unit of graphemeUnits(value)) {
|
|
36
|
+
if (truncated.length + unit.length > maxChars) break;
|
|
37
|
+
truncated += unit;
|
|
38
|
+
}
|
|
39
|
+
return truncated;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Grapheme clusters of `value` as separate strings, or its code points when
|
|
44
|
+
* Intl.Segmenter is unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export function graphemeUnits(value: string): string[] {
|
|
47
|
+
const segmenter = getGraphemeSegmenter();
|
|
48
|
+
if (segmenter === null) return [...value];
|
|
49
|
+
return Array.from(segmenter.segment(value), (segment) => segment.segment);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const COMBINING_MARK = /\p{M}/u;
|
|
53
|
+
const FORMAT_CHAR = /\p{Cf}/u;
|
|
54
|
+
const EMOJI_PRESENTATION = /\p{Emoji_Presentation}/u;
|
|
55
|
+
const EMOJI_PRESENTATION_SELECTOR = "\uFE0F";
|
|
56
|
+
|
|
57
|
+
// ponytail: pragmatic East_Asian_Width Wide/Fullwidth subset - contiguous
|
|
58
|
+
// block ranges plus the emoji presentation planes. Sparse exceptions inside
|
|
59
|
+
// those ranges (narrow unassigned code points, U+303F) do not matter for
|
|
60
|
+
// terminal column alignment; switch to a full EAW table only if drift shows.
|
|
61
|
+
const WIDE_CODE_POINT_RANGES: ReadonlyArray<readonly [number, number]> = [
|
|
62
|
+
[0x1100, 0x115f], // Hangul Jamo
|
|
63
|
+
[0x2e80, 0x303e], // CJK radicals, Kangxi radicals, CJK symbols and punctuation
|
|
64
|
+
[0x3041, 0x33ff], // Hiragana, Katakana, Bopomofo, Hangul compatibility jamo
|
|
65
|
+
[0x3400, 0x4dbf], // CJK unified ideographs extension A
|
|
66
|
+
[0x4e00, 0xa4cf], // CJK unified ideographs, Yi
|
|
67
|
+
[0xa960, 0xa97f], // Hangul Jamo extension A
|
|
68
|
+
[0xac00, 0xd7a3], // Hangul syllables
|
|
69
|
+
[0xf900, 0xfaff], // CJK compatibility ideographs
|
|
70
|
+
[0xfe10, 0xfe19], // Vertical forms
|
|
71
|
+
[0xfe30, 0xfe6b], // CJK compatibility forms
|
|
72
|
+
[0xff00, 0xff60], // Fullwidth ASCII and punctuation
|
|
73
|
+
[0xffe0, 0xffe6], // Fullwidth signs
|
|
74
|
+
[0x1f300, 0x1f64f], // Emoji symbols, pictographs, emoticons
|
|
75
|
+
[0x1f680, 0x1f6ff], // Transport and map
|
|
76
|
+
[0x1f900, 0x1f9ff], // Supplemental symbols
|
|
77
|
+
[0x1fa70, 0x1faff], // Symbols and pictographs extended-A
|
|
78
|
+
[0x20000, 0x2fffd], // CJK extensions B-F
|
|
79
|
+
[0x30000, 0x3fffd], // CJK extension G and later
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
function isWideGlyph(glyph: string): boolean {
|
|
83
|
+
const codePoint = glyph.codePointAt(0)!;
|
|
84
|
+
let low = 0;
|
|
85
|
+
let high = WIDE_CODE_POINT_RANGES.length - 1;
|
|
86
|
+
while (low <= high) {
|
|
87
|
+
const mid = (low + high) >> 1;
|
|
88
|
+
const [start, end] = WIDE_CODE_POINT_RANGES[mid]!;
|
|
89
|
+
if (codePoint < start) high = mid - 1;
|
|
90
|
+
else if (codePoint > end) low = mid + 1;
|
|
91
|
+
else return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Terminal cell width: East_Asian_Width Wide/Fullwidth glyphs count as 2,
|
|
98
|
+
* combining marks and format characters (ZWJ, variation selectors) as 0,
|
|
99
|
+
* everything else as 1. A grapheme cluster with emoji presentation - ZWJ
|
|
100
|
+
* sequences, flags, skin-tone modifiers - occupies two cells.
|
|
101
|
+
*/
|
|
102
|
+
export function displayWidth(value: string): number {
|
|
103
|
+
let width = 0;
|
|
104
|
+
for (const cluster of graphemeUnits(value)) {
|
|
105
|
+
width += graphemeClusterWidth(cluster);
|
|
106
|
+
}
|
|
107
|
+
return width;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function graphemeClusterWidth(cluster: string): number {
|
|
111
|
+
if (cluster.includes(EMOJI_PRESENTATION_SELECTOR)) return 2;
|
|
112
|
+
let emojiCluster = false;
|
|
113
|
+
let width = 0;
|
|
114
|
+
for (const glyph of cluster) {
|
|
115
|
+
if (COMBINING_MARK.test(glyph) || FORMAT_CHAR.test(glyph)) continue;
|
|
116
|
+
if (EMOJI_PRESENTATION.test(glyph)) emojiCluster = true;
|
|
117
|
+
width += isWideGlyph(glyph) ? 2 : 1;
|
|
118
|
+
}
|
|
119
|
+
return emojiCluster ? 2 : width;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* padEnd analog that pads to a terminal cell width, so rows containing CJK
|
|
124
|
+
* or emoji keep their columns aligned.
|
|
125
|
+
*/
|
|
126
|
+
export function padEndDisplay(value: string, targetWidth: number): string {
|
|
127
|
+
const padding = targetWidth - displayWidth(value);
|
|
128
|
+
return padding > 0 ? value + " ".repeat(padding) : value;
|
|
129
|
+
}
|
package/dist/chunk-6MKAMLQL.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// src/whitespace.ts
|
|
2
|
-
function collapseWhitespace(value) {
|
|
3
|
-
return value.replace(/\s+/g, " ").trim();
|
|
4
|
-
}
|
|
5
|
-
function truncateCodePointSafe(value, maxChars) {
|
|
6
|
-
const glyphs = Array.from(value);
|
|
7
|
-
if (maxChars <= 0) return "";
|
|
8
|
-
if (glyphs.length <= maxChars) return value;
|
|
9
|
-
return glyphs.slice(0, Math.max(1, maxChars)).join("").trimEnd();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
collapseWhitespace,
|
|
14
|
-
truncateCodePointSafe
|
|
15
|
-
};
|
|
16
|
-
//# sourceMappingURL=chunk-6MKAMLQL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/whitespace.ts"],"sourcesContent":["export function collapseWhitespace(value: string): string {\n return value.replace(/\\s+/g, \" \").trim();\n}\n\nexport function truncateCodePointSafe(value: string, maxChars: number): string {\n const glyphs = Array.from(value);\n if (maxChars <= 0) return \"\";\n if (glyphs.length <= maxChars) return value;\n return glyphs.slice(0, Math.max(1, maxChars)).join(\"\").trimEnd();\n}\n"],"mappings":";AAAO,SAAS,mBAAmB,OAAuB;AACxD,SAAO,MAAM,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACzC;AAEO,SAAS,sBAAsB,OAAe,UAA0B;AAC7E,QAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,MAAI,YAAY,EAAG,QAAO;AAC1B,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,OAAO,MAAM,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ;AACjE;","names":[]}
|