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">×</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 }
0 commit comments