From 0c3eaf38853892319640c9d4fe4b901066a0f9fc Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 20 Apr 2026 22:37:51 +0700 Subject: [PATCH] fix(wordle): align markers with letters using fullwidth Latin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Color-square emoji (🟩🟨⬜) render at ~2 monospace cells wide in Telegram's
 blocks; the previous letter row used " X " (3 cells
per letter = 15 cells total for a 5-letter word) against markers that
only span ~10 cells, so columns drifted.

Switching letters to fullwidth Latin (U+FF21..U+FF3A, e.g. 'A' instead
of 'A') puts each letter at exactly 2 cells via East Asian Width =
Fullwidth, matching one emoji per letter with no padding:

  🟩🟨⬜🟩🟩
  CRANE

No spacing heuristics — alignment is a consequence of character width,
which every monospace-capable Telegram client respects.
---
 src/modules/wordle/render.js | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/modules/wordle/render.js b/src/modules/wordle/render.js
index 3678fbf..5210794 100644
--- a/src/modules/wordle/render.js
+++ b/src/modules/wordle/render.js
@@ -15,9 +15,22 @@
 
 const MARKER = { correct: "🟩", partial: "🟨", wrong: "⬜" };
 
+// In a Telegram 
 block, color-square emoji render at ~2 monospace cells
+// wide, while ASCII letters render at 1 cell — so a plain "A B C" row never
+// lines up under the markers. Fullwidth Latin (U+FF21..U+FF3A) is East Asian
+// Width = Fullwidth, which renders at exactly 2 cells per character and
+// matches the emoji column width 1-to-1.
+const FULLWIDTH_OFFSET = 0xff21 - 0x41; // 'A' (0x41) → 'A' (0xFF21)
+
+function toFullwidthUpper(ch) {
+  const code = ch.toUpperCase().charCodeAt(0);
+  if (code >= 0x41 && code <= 0x5a) return String.fromCharCode(code + FULLWIDTH_OFFSET);
+  return ch;
+}
+
 function rowPair(results) {
   const markers = results.map((r) => MARKER[r.result] ?? "⬜").join("");
-  const letters = results.map((r) => ` ${r.letter.toUpperCase()} `).join("");
+  const letters = results.map((r) => toFullwidthUpper(r.letter)).join("");
   return `${markers}\n${letters}`;
 }