Skip to content

Commit 7a945e5

Browse files
jsollycursoragent
andauthored
Highlight CO after “contracting officer” in chapters (#77)
* Link glossary acronyms even after the full phrase CO was already an alias of Contracting Officer, but once-per-term deduping skipped the acronym after “contracting officer” linked first. Allow one phrase link and one acronym link per term so CO highlights in chapters like early-exchange-lawful-limited. * Clarify chapter glossary link dedupe comment Match the phrase-plus-acronym linking behavior in ChapterView. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent c557afc commit 7a945e5

2 files changed

Lines changed: 14 additions & 11 deletions

File tree

src/components/app/ChapterView.svelte

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,22 @@
3434
return questionsForChapter(action.chapterId).length > 0;
3535
});
3636
37-
/** One pass per chapter so each glossary term links at most once. */
37+
/** One pass per chapter: each term links once as a phrase and once as an acronym. */
3838
let linkedCopy = $derived.by(() => {
3939
const current = chapter;
4040
if (!current) return null;
41-
const linkedIds = new Set<string>();
41+
const linkedKeys = new Set<string>();
4242
return {
43-
intro: segmentGlossaryText(current.intro, linkedIds),
43+
intro: segmentGlossaryText(current.intro, linkedKeys),
4444
pieces: current.pieces.map((piece) => ({
4545
id: piece.id,
46-
teach: segmentGlossaryText(piece.teach, linkedIds),
46+
teach: segmentGlossaryText(piece.teach, linkedKeys),
4747
watchFor: piece.watchFor
48-
? segmentGlossaryText(piece.watchFor, linkedIds)
48+
? segmentGlossaryText(piece.watchFor, linkedKeys)
4949
: null,
5050
})),
5151
closing: current.closing
52-
? segmentGlossaryText(current.closing, linkedIds)
52+
? segmentGlossaryText(current.closing, linkedKeys)
5353
: null,
5454
};
5555
});

src/lib/far/link-glossary-terms.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,12 @@ function collectHits(
111111
}
112112

113113
/**
114-
* Split plain text into text/term segments. Mutates `linkedIds` so each term id
115-
* is linked at most once across a chapter (pass the same Set to every field).
114+
* Split plain text into text/term segments. Mutates `linkedKeys` so each term
115+
* is linked at most once per surface class across a chapter (phrase once and
116+
* acronym once — e.g. “contracting officer” and later “CO”). Pass the same
117+
* Set to every field in a chapter.
116118
*/
117-
export function segmentGlossaryText(text: string, linkedIds: Set<string>): TextSegment[] {
119+
export function segmentGlossaryText(text: string, linkedKeys: Set<string>): TextSegment[] {
118120
if (!text) return [];
119121

120122
const { insensitive, sensitive, insensitiveLookup, sensitiveLookup } = matchEngine();
@@ -128,13 +130,14 @@ export function segmentGlossaryText(text: string, linkedIds: Set<string>): TextS
128130

129131
for (const hit of hits) {
130132
if (hit.index < lastIndex) continue;
131-
if (linkedIds.has(hit.termId)) continue;
133+
const key = `${hit.termId}:${isAcronymLabel(hit.text) ? "acronym" : "phrase"}`;
134+
if (linkedKeys.has(key)) continue;
132135

133136
if (hit.index > lastIndex) {
134137
segments.push({ kind: "text", text: text.slice(lastIndex, hit.index) });
135138
}
136139

137-
linkedIds.add(hit.termId);
140+
linkedKeys.add(key);
138141
segments.push({ kind: "term", text: hit.text, termId: hit.termId });
139142
lastIndex = hit.index + hit.text.length;
140143
}

0 commit comments

Comments
 (0)