From e9ebd318cd7be92819cb6a48967f55a0a9002b9e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 7 Jun 2026 13:02:16 -0400 Subject: [PATCH] End-dominated vowel fusion; annotation lines render dimmer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- app.py | 9 ++++++++- static/index.html | 8 +++++--- tests/test_rhymes.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index eee450c..901a03c 100644 --- a/app.py +++ b/app.py @@ -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) diff --git a/static/index.html b/static/index.html index 074f661..5635d48 100644 --- a/static/index.html +++ b/static/index.html @@ -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); diff --git a/tests/test_rhymes.py b/tests/test_rhymes.py index 8dc81df..88208a8 100644 --- a/tests/test_rhymes.py +++ b/tests/test_rhymes.py @@ -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")