Skip to content

Commit fe9081b

Browse files
committed
fix(macronizer): honest popup labels, dedup Morpheus rows, tag-disagreement note
Three UI fixes surfaced by the currito popup, plus the italorum verse override: - Morpheus analyses table deduplicated (identical lemma+accented+features rows collapsed; Morpheus emits repeats across case-variant runs). - "Wordlist: Found" no longer lies for Morpheus-rescued words — extras only exist for words the file lacks, so label them "Not found — via Morpheus" using morpheusAnalyzed as the signal. - RFTagger POS is compared against the selected reading's POS; a note flags the disagreement (e.g. currito tagged adverb, read as verb). - ACCENT_OVERRIDES: italorum carries both Ĭtalōrum (prose) and Ītalōrum (hendecasyllable position 8) as scansion candidates; the 33MB wordlist is regenerated upstream and would swallow a one-off data edit. Verified by e2e/popup-check.spec.js (dedup to 1 row, note shown, honest label) and the full suite (22 passed).
1 parent bfb5035 commit fe9081b

10 files changed

Lines changed: 105 additions & 27 deletions

wiktionary_pron/macronizer.html

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
.verse-foot { font-family: 'Courier New', Courier, monospace; font-weight: bold; font-size: 13px;
9393
color: #6a1b9a; background: #f3e5f5; padding: 1px 8px; border-radius: 4px;
9494
margin-left: 12px; white-space: nowrap; vertical-align: middle; }
95+
.verse-foot.no-scan { color: #999; background: #f0f0f0; font-style: italic; }
9596
/* ----- Word detail popup (hover) ----- */
9697
/* fixed, not absolute: an absolutely-positioned popup adds to the document height,
9798
which makes the page grow/shrink (and the scrollbar appear/disappear) as it opens
@@ -808,6 +809,7 @@ <h2 class="result-heading">Macronized</h2>
808809
if (encliticText) flags.push('Enclitic -' + encliticText);
809810

810811
let candidatesHtml = '';
812+
let activeReadingTag = null; // reading's LDT tag, if any — for the RFTagger note below
811813
if (candidates.length > 0) {
812814
// Pair each chip (breve-marked, so quantity-only variants stay distinct) with the
813815
// plain form actually rendered in the text, so we can mark the one on screen now.
@@ -829,6 +831,9 @@ <h2 class="result-heading">Macronized</h2>
829831
tag: src && src.tag ? src.tag : null
830832
});
831833
});
834+
// The reading currently on screen — used to flag RFTagger disagreement below.
835+
const activeReading = pairs.find(p => p.plain === displayText);
836+
if (activeReading && activeReading.tag) activeReadingTag = activeReading.tag;
832837
const spellings = new Set(pairs.map(p => p.plain));
833838
const canCycle = spellings.size > 1;
834839
const title = pairs.length > spellings.size
@@ -862,15 +867,30 @@ <h2 class="result-heading">Macronized</h2>
862867

863868
let morpheusHtml = '';
864869
const analyses = token.morpheusResults && token.morpheusResults.analyses || [];
865-
if (analyses.length > 0) {
870+
// Dedup: Morpheus can emit the same parse multiple times (case-variant runs,
871+
// repeated <NL> lines). Key on what the row actually shows — lemma, accented,
872+
// and the rendered feature list — so identical-looking rows collapse even when
873+
// the underlying formInfo objects differ in which fields they carry.
874+
const seenParses = new Set();
875+
const analysesWithKey = analyses.map((a) => {
876+
const fi = a.formInfo || {};
877+
const feats = [fi.partOfSpeech, fi.case, fi.number, fi.gender, fi.tense, fi.mood, fi.voice]
878+
.filter(Boolean).join(', ');
879+
const key = (a.lemma || '') + '|' + (a.accented || '') + '|' + feats;
880+
return { a, feats, key };
881+
});
882+
const uniqueAnalyses = [];
883+
for (const { a, feats, key } of analysesWithKey) {
884+
if (seenParses.has(key)) continue;
885+
seenParses.add(key);
886+
uniqueAnalyses.push({ a, feats });
887+
}
888+
if (uniqueAnalyses.length > 0) {
866889
morpheusHtml = '<div class="popup-section"><div class="popup-section-title">Morpheus analyses</div><table>';
867-
analyses.slice(0, 8).forEach((a, idx) => {
868-
const fi = a.formInfo || {};
869-
const feats = [fi.partOfSpeech, fi.case, fi.number, fi.gender, fi.tense, fi.mood, fi.voice]
870-
.filter(Boolean).join(', ');
890+
uniqueAnalyses.slice(0, 8).forEach(({ a, feats }, idx) => {
871891
morpheusHtml += `<tr><td>${idx+1}. ${esc(a.lemma || '—')}</td><td>${esc(a.accented || '—')}${feats ? '<br><span class="tag-desc">' + esc(feats) + '</span>' : ''}</td></tr>`;
872892
});
873-
if (analyses.length > 8) morpheusHtml += `<tr><td></td><td>… and ${analyses.length - 8} more</td></tr>`;
893+
if (uniqueAnalyses.length > 8) morpheusHtml += `<tr><td></td><td>… and ${uniqueAnalyses.length - 8} more</td></tr>`;
874894
morpheusHtml += '</table></div>';
875895
}
876896

@@ -882,18 +902,42 @@ <h2 class="result-heading">Macronized</h2>
882902
'</table></div>'
883903
: '';
884904

905+
// RFTagger and the readings can disagree on part of speech (rare/ambiguous forms,
906+
// e.g. currito tagged adverb by RFTagger but read as a verb). Say so rather than
907+
// presenting both as if they agree.
908+
let tagNote = '';
909+
const tagPos = (t) => {
910+
if (!t) return null;
911+
const pos = decodeLdtTagToFeatures(t).find(f => f.feature === 'POS');
912+
return pos ? pos.value : null;
913+
};
914+
const tagPosVal = tagPos(token.tag);
915+
const readingPosVal = tagPos(activeReadingTag);
916+
if (tagPosVal && readingPosVal && tagPosVal !== readingPosVal) {
917+
tagNote = '<div class="popup-note">RFTagger classifies this as <b>' + esc(tagPosVal) +
918+
'</b>, but the selected reading is a <b>' + esc(readingPosVal) +
919+
'</b> — rare or ambiguous forms are often mistagged; the analyzed reading below is more reliable.</div>';
920+
}
921+
885922
return '<button type="button" class="popup-close" aria-label="Close">&times;</button>' +
886923
`<h4>${esc(token.text)}${encliticText ? esc(encliticText) : ''}${esc(displayText)}</h4>` +
887-
tagHtml +
924+
tagHtml + tagNote +
888925
'<div class="popup-section"><div class="popup-section-title">Details</div><table>' +
889926
// The token-level lemma is a frequency guess used to RANK the readings — it is not
890927
// the lemma of any particular reading (those are in the readings table below).
891928
`<tr><td title="The most frequent lemma for this spelling. Used to rank the readings below.">` +
892929
`Assumed lemma:</td><td>${esc(token.lemma && token.lemma !== '-' ? token.lemma : '—')}</td></tr>` +
893-
`<tr><td>Wordlist:</td><td>${token.isUnknown ? 'Not found' : 'Found'}</td></tr>` +
930+
// morpheusAnalyzed is the reliable "absent from the wordlist file" signal:
931+
// Morpheus extras are only ever written for words the file lacks (see
932+
// WordlistEngine.addEntry). isUnknown is true only when getAccents found
933+
// nothing at all — but ensureAnalyzed runs first, so a Morpheus-rescued
934+
// word has extras and isUnknown stays false. Label honestly.
935+
`<tr><td>Wordlist:</td><td>${token.morpheusAnalyzed
936+
? 'Not found — via Morpheus'
937+
: token.isUnknown ? 'Not found' : 'Found'}</td></tr>` +
894938
// Morpheus IS run on every unknown word (Macronizer.ensureAnalyzed); when it finds
895939
// nothing the flag simply never gets set. "Not analyzed" read as "we didn't try".
896-
(token.isUnknown
940+
(token.isUnknown || token.morpheusAnalyzed
897941
? `<tr><td>Morpheus:</td><td>${token.morpheusAnalyzed
898942
? 'Analyzed'
899943
: 'No analysis found'}</td></tr>` +
@@ -1090,7 +1134,9 @@ <h2 class="result-heading">Macronized</h2>
10901134
const span = document.createElement('span');
10911135
span.className = 'ipa';
10921136
span.setAttribute('content', displayText);
1093-
1137+
// TODO
1138+
// make text selectable
1139+
//span.textContent = displayText;
10941140
const candidates = [...new Set(
10951141
(token.accented || []).map(c => matchCase(token.text, candidateToDisplay(c)))
10961142
)].map(c => encliticText ? c + encliticText : c);
@@ -1148,12 +1194,15 @@ <h2 class="result-heading">Macronized</h2>
11481194
}
11491195

11501196
const foot = scannedFeet[lineIdx];
1197+
const footSpan = document.createElement('span');
1198+
footSpan.className = 'verse-foot';
11511199
if (foot) {
1152-
const footSpan = document.createElement('span');
1153-
footSpan.className = 'verse-foot';
11541200
footSpan.textContent = foot;
1155-
td.appendChild(footSpan);
1201+
} else {
1202+
footSpan.classList.add('no-scan');
1203+
footSpan.textContent = '—';
11561204
}
1205+
td.appendChild(footSpan);
11571206
tr.appendChild(td);
11581207
tbody.appendChild(tr);
11591208
}

wiktionary_pron/macronizer/dist/analysis/MorpheusAnalyzer.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wiktionary_pron/macronizer/dist/analysis/MorpheusAnalyzer.js

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)