@hraness/wordcell 0.22.2 → 0.22.4
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 +4 -4
- package/dist/cli.js +4 -4
- package/dist/{index-3agn8scn.js → index-23m6bbjt.js} +47 -11
- package/dist/{index-bw2r37z9.js → index-9h7zh0wt.js} +1 -1
- package/dist/{index-hmw17zaa.js → index-pnt9wr59.js} +516 -230
- package/dist/index.js +2 -2
- package/dist/publish-reader/reader.js +48 -12
- package/dist/publish-search.js +1 -1
- package/dist/publish.js +2 -2
- package/dist/{support-p452p9c4.js → support-m3t917ev.js} +3 -3
- package/package.json +3 -3
- package/skills/wordcell/SKILL.md +1 -1
- package/src/publish-index.ts +3 -2
- package/src/publish-markdown.ts +400 -122
- package/src/publish-model.ts +2 -2
- package/src/publish-reader/reader.ts +3 -1
- package/src/publish-search.ts +44 -13
- package/src/publish.ts +4 -1
package/src/publish-model.ts
CHANGED
|
@@ -47,7 +47,7 @@ export const WORDCELL_SITE_LIMITS_V1 = Object.freeze({
|
|
|
47
47
|
* exact lane's substring semantics; larger corpora use postings shards.
|
|
48
48
|
*/
|
|
49
49
|
inlineTotalBytes: 2 * 1_024 * 1_024,
|
|
50
|
-
/** Normalized content
|
|
50
|
+
/** Normalized readable content retained per note payload for snippet hydration. */
|
|
51
51
|
noteTextBytes: 32 * 1_024,
|
|
52
52
|
/** Site title and description. */
|
|
53
53
|
titleBytes: 512,
|
|
@@ -185,7 +185,7 @@ export type WordcellSiteNoteV1 = {
|
|
|
185
185
|
readonly type?: string;
|
|
186
186
|
readonly tags: readonly string[];
|
|
187
187
|
readonly summary: string;
|
|
188
|
-
/** Bounded normalized content
|
|
188
|
+
/** Bounded normalized readable content for snippet hydration, not ranking. */
|
|
189
189
|
readonly text: string;
|
|
190
190
|
readonly textTruncated: boolean;
|
|
191
191
|
readonly links: readonly WordcellSiteNoteLinkV1[];
|
|
@@ -321,7 +321,9 @@ function resultHref(base: string, slug: string): string {
|
|
|
321
321
|
|
|
322
322
|
function snippetFor(index: SiteIndex, doc: WordcellSiteDocV1, query: PublishQuery): string {
|
|
323
323
|
const hydrated = index.notes.get(doc.s);
|
|
324
|
-
|
|
324
|
+
// doc.x is a normalized ranking basis, not display text. The eager preview
|
|
325
|
+
// remains readable while the existing bounded hydration fetch is pending.
|
|
326
|
+
const text = hydrated?.text;
|
|
325
327
|
return text === undefined ? doc.p : publishSnippet(text, query, doc.p);
|
|
326
328
|
}
|
|
327
329
|
|
package/src/publish-search.ts
CHANGED
|
@@ -392,8 +392,10 @@ export function comparePublishScores(
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
/**
|
|
395
|
-
* Bounded context
|
|
396
|
-
*
|
|
395
|
+
* Bounded display context around the first normalized query match. Match
|
|
396
|
+
* offsets are mapped back to NFC display text before taking a UTF-8 byte
|
|
397
|
+
* window, so case expansion and supplementary characters cannot split it.
|
|
398
|
+
* The fallback retains the separate document-preview byte contract.
|
|
397
399
|
*/
|
|
398
400
|
export function publishSnippet(
|
|
399
401
|
text: string,
|
|
@@ -401,18 +403,47 @@ export function publishSnippet(
|
|
|
401
403
|
fallback: string,
|
|
402
404
|
windowBytes = 160,
|
|
403
405
|
): string {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
406
|
+
if (!Number.isSafeInteger(windowBytes) || windowBytes < 0) throw new RangeError("Snippet window must be a nonnegative safe byte count.");
|
|
407
|
+
const display = text.normalize("NFC");
|
|
408
|
+
const normalized = publishNormalize(display);
|
|
409
|
+
let match = query.normalized === "" ? -1 : normalized.indexOf(query.normalized);
|
|
410
|
+
if (match < 0) {
|
|
407
411
|
for (const term of query.terms) {
|
|
408
|
-
|
|
409
|
-
if (
|
|
412
|
+
match = normalized.indexOf(term);
|
|
413
|
+
if (match >= 0) break;
|
|
410
414
|
}
|
|
411
415
|
}
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
if (match < 0) return fallback;
|
|
417
|
+
let offset = 0, normalizedOffset = 0;
|
|
418
|
+
for (const character of display) {
|
|
419
|
+
const width = character.toLocaleLowerCase("en-US").length;
|
|
420
|
+
if (normalizedOffset + width > match) break;
|
|
421
|
+
normalizedOffset += width;
|
|
422
|
+
offset += character.length;
|
|
423
|
+
}
|
|
424
|
+
const edge = windowBytes >= 6 ? "…" : "";
|
|
425
|
+
const budget = windowBytes - 2 * publishUtf8Bytes(edge);
|
|
426
|
+
const previous = (end: number): number => {
|
|
427
|
+
const last = display.charCodeAt(end - 1);
|
|
428
|
+
return end > 1 && last >= 0xdc00 && last <= 0xdfff && display.charCodeAt(end - 2) >= 0xd800 && display.charCodeAt(end - 2) <= 0xdbff ? end - 2 : end - 1;
|
|
429
|
+
};
|
|
430
|
+
let start = offset, end = offset, used = 0;
|
|
431
|
+
while (start > 0) {
|
|
432
|
+
const before = previous(start), bytes = publishUtf8Bytes(display.slice(before, start));
|
|
433
|
+
if (used + bytes > Math.floor(budget / 2)) break;
|
|
434
|
+
start = before; used += bytes;
|
|
435
|
+
}
|
|
436
|
+
while (end < display.length) {
|
|
437
|
+
const character = String.fromCodePoint(display.codePointAt(end) ?? 0), bytes = publishUtf8Bytes(character);
|
|
438
|
+
if (used + bytes > budget) break;
|
|
439
|
+
end += character.length; used += bytes;
|
|
440
|
+
}
|
|
441
|
+
// Near the end, spend the remaining budget on earlier whole characters.
|
|
442
|
+
while (start > 0) {
|
|
443
|
+
const before = previous(start), bytes = publishUtf8Bytes(display.slice(before, start));
|
|
444
|
+
if (used + bytes > budget) break;
|
|
445
|
+
start = before; used += bytes;
|
|
446
|
+
}
|
|
447
|
+
const snippet = display.slice(start, end).replace(/\s+/gu, " ").trim();
|
|
448
|
+
return `${start > 0 ? edge : ""}${snippet}${end < display.length ? edge : ""}`;
|
|
418
449
|
}
|
package/src/publish.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./graph.js";
|
|
17
17
|
import { buildSiteIndex } from "./publish-index.js";
|
|
18
18
|
import {
|
|
19
|
+
projectMarkdownText,
|
|
19
20
|
renderMarkdownToHtml,
|
|
20
21
|
type PublishRenderContext,
|
|
21
22
|
} from "./publish-markdown.js";
|
|
@@ -307,7 +308,9 @@ export async function projectVault(
|
|
|
307
308
|
let anyTextTruncated = false;
|
|
308
309
|
for (const note of selection.notes) {
|
|
309
310
|
const slug = slugFor(note.id);
|
|
310
|
-
|
|
311
|
+
// Display grammar owns citations before normalization can fold distinct
|
|
312
|
+
// labels together. Ranking continues to use untouched searchableText.
|
|
313
|
+
const text = boundedNormalized(projectMarkdownText(note.content).text, WORDCELL_SITE_LIMITS_V1.noteTextBytes);
|
|
311
314
|
anyTextTruncated ||= text.truncated;
|
|
312
315
|
const payload: WordcellSiteNoteV1 = {
|
|
313
316
|
format: WORDCELL_SITE_NOTE_FORMAT_V1,
|