From 14e8dac177d24790efac43f1c9bb0c3ed1b75060 Mon Sep 17 00:00:00 2001 From: Byntham <135178921+Byntham@users.noreply.github.com> Date: Thu, 5 Nov 2026 03:22:04 -0800 Subject: [PATCH] Turn off kerning and ligatures in the composer so typed letters stay put Each typed letter is its own span, so the font cannot kern or ligate across the boundary between typed text and the ghost text. Every keystroke moved that boundary and nudged later letters by up to 0.85px in Firefox. Co-Authored-By: Claude Opus 5.5 --- src/components/chat/ChatComposer.tsx | 2 +- tests/play.mjs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/chat/ChatComposer.tsx b/src/components/chat/ChatComposer.tsx index 6c6d97c..1cd28e1 100644 --- a/src/components/chat/ChatComposer.tsx +++ b/src/components/chat/ChatComposer.tsx @@ -85,7 +85,7 @@ function GhostEditor({ data-testid="composer-editor" tabIndex={0} className={cn( - "composer-tiptap -m-1 block max-h-[8lh] min-h-[calc(2lh+--spacing(2))] overflow-y-auto p-1 whitespace-pre-wrap wrap-break-word bg-transparent leading-relaxed focus:outline-none", + "composer-tiptap -m-1 block [font-kerning:none] [font-variant-ligatures:none] max-h-[8lh] min-h-[calc(2lh+--spacing(2))] overflow-y-auto p-1 whitespace-pre-wrap wrap-break-word bg-transparent leading-relaxed focus:outline-none", complete ? "text-success-foreground" : "text-foreground", )} > diff --git a/tests/play.mjs b/tests/play.mjs index 5c6279d..54a735b 100644 --- a/tests/play.mjs +++ b/tests/play.mjs @@ -255,6 +255,10 @@ async function checkWordsStayPutWhileTyping(browser) { if (shifted >= 0) moved.push(`${width}px "${text.slice(0, n).slice(-12)}|" moved "${text.slice(shifted, shifted + 8)}"`); } } + const shaping = await page + .locator('[data-testid="composer-editor"]') + .evaluate((el) => `${getComputedStyle(el).fontKerning} ${getComputedStyle(el).fontVariantLigatures}`); + check(shaping === "none none", `composer turns off kerning and ligatures so letters keep their x position (got ${shaping})`); check(wrapped >= 5, `prompt wraps at ${wrapped} of 11 widths`); check(moved.length === 0, `no character changes line while typing${moved.length ? ` (${moved.length} keys): ${moved.slice(0, 3).join("; ")}` : ""}`); check(errors.length === 0, `no console errors in wrap run${errors.length ? `: ${errors.join(" | ")}` : ""}`);