End-dominated vowel fusion; annotation lines render dimmer

- Same-vowel perfect families fuse when both live at line ends:
  blood/mud + thugs/drugs (+does/love/enough) read as Kanye's one AH
  chain; mid-line families keep their codas distinct
- Annotation lines (#, [, ( starts) render in a dim ink — the
  highlight layer now owns the glyph color (textarea text is
  transparent over it), in the editor and the PNG export

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 13:02:16 -04:00
parent 5e7845f0bb
commit e9ebd318cd
3 changed files with 25 additions and 4 deletions
+8 -1
View File
@@ -948,6 +948,9 @@ def analyze(draft: Draft):
i = parent[i]
return i
def _endy(gi):
return sum(t["is_end"] for t in raw_groups[gi]["toks"]) >= 2
for ai in range(len(sv)):
for bi in range(ai + 1, len(sv)):
gi, (va, ca) = sv[ai]
@@ -955,7 +958,11 @@ def analyze(draft: Draft):
if va != vb or ca == cb:
continue
s, l = (ca, cb) if len(ca) < len(cb) else (cb, ca)
if l[:len(s)] == s or l[len(l) - len(s):] == s:
nested = l[:len(s)] == s or l[len(l) - len(s):] == s
# end-dominated families rhyme on their bare vowel, the way
# lone endings always could (blood/mud + thugs/drugs — the
# Kanye chorus chain); mid-line families keep their codas
if nested or (_endy(gi) and _endy(gj)):
parent[find(gi)] = find(gj)
clusters = defaultdict(list)
+5 -3
View File
@@ -110,9 +110,10 @@
.editor-shell.rhythm #editor { line-height: 2.35; }
#highlight {
pointer-events: none;
color: transparent;
color: var(--ink);
z-index: 1;
}
#highlight .anno { color: #6a5f52; }
#stresslayer {
pointer-events: none;
color: transparent;
@@ -126,8 +127,9 @@
}
#editor {
background: transparent;
color: var(--ink);
color: transparent;
caret-color: var(--accent);
&::selection { background: rgba(232,129,74,0.22); }
resize: none;
z-index: 2;
outline: none;
@@ -902,7 +904,7 @@ document.getElementById('exportBtn').addEventListener('click', async ()=>{
x.globalAlpha = 1;
});
}
x.fillStyle = ink;
x.fillStyle = /^\s*[#([]/.test(line) ? '#6a5f52' : ink;
x.fillText(line, PAD, y);
if(rhythm && fresh){
const spans = (analysis.stress.filter(s=>s.l===i)).sort((a,b)=>a.s-b.s);
+12
View File
@@ -630,3 +630,15 @@ def test_paren_words_highlight_but_never_end():
ends = {res["lines"][t["l"]][t["s"]:t["e"]].lower()
for t in res["tokens"] if t["end"] and not t["ph"]}
assert "greed" not in ends
def test_end_dominated_vowel_families_fuse():
# the Kanye chorus chain: blood/mud (AH D) and thugs/drugs (AH G Z)
# are one AH family when both live at line ends
text = ("Shower us with your love\n"
"Wash us in the blood\n"
"Drop this for the thugs\n"
"Know I grew up in the mud\n"
"The top is not enough\n"
"No choice, sellin' drugs")
group_with(text, "love", "blood", "thugs", "mud", "enough", "drugs")