Skim v1.0.19
Skim v1.0.19 — "Half a translation is not a translation" 🌍
The one where "Translate" stops giving up halfway down the email and wandering off. Turns out newsletters have been smuggling hundreds of invisible characters past us, and the model was dutifully copying every single one back. 🫥
🙌 This whole release belongs to @gildas-refacto
Every single fix below came from #30 — their fourth merged PR, after #16, #18 and #29. And not "hey, translation looks broken" either: three separate root causes traced down to the individual Unicode code point, each fixed as narrowly as possible, each backed by real measurements over 20 runs — plus a test pinning the characters that must not be touched, because breaking Persian and emoji to fix newsletters would be a bad trade.
That is an absurdly high bar for an outside contribution. Thank you. 💜
🐛 Fixed
- Translations no longer stop mid-email. Newsletters pad their preheader with hundreds of zero-width characters so your inbox preview cuts off at the teaser. They're invisible to you, invisible to
split_whitespace, and extremely visible to the model — which, told to be faithful, copied them back with interest until it ran out of output budget. On one real newsletter: 218 invisible characters in, 2484 out, 1 of 7 segments translated in 84 seconds. With them filtered: 17 of 17 in 21 seconds. 🧹 - The subject gets translated now. We were handing the model the subject twice — once as a
Subject:header, once as actual work. Sitting next to aFrom:line, the pair read like an RFC 822 header block, and the model politely preserved it instead of translating. Result: a translated body wearing an untranslated hat. 🎩 Measured over 20 runs: verbatim 7/20 before, 0/20 now. - A half-finished translation no longer gets cached as a finished one. Previously Skim only rejected a reply that came back completely empty, so a truncated one got filed as gospel — and since the cache row is what makes the pane open translated, pressing Translate again just handed you the same half-English mess. Now Skim checks each segment by number (a reply that skips one id and invents another keeps the count intact — sneaky) and flags it honestly. ♻️
- Paragraphs no longer vanish into the void. A stream cut off right after a
[[n]]marker used to count as a real answer, splice an empty string over your paragraph, and remove it from the email permanently. It doesn't do that anymore. 🕳️
🧐 Deliberately left alone
- U+200C and U+200D (they join emoji sequences and shape Persian and Devanagari words) and U+200E/U+200F (they carry text direction) are not treated as padding. Stripping those would corrupt the actual mail, so there's now a test pinning them as survivors. The blast radius is narrow on purpose. 🎯
🧹 Housekeeping
- New tests around empty inline elements, invented segment ids, and markers with nothing after them — the three ways this quietly went wrong. 🧪