diff --git a/src/modules/wordle/handlers.js b/src/modules/wordle/handlers.js index 8512456..1455b92 100644 --- a/src/modules/wordle/handlers.js +++ b/src/modules/wordle/handlers.js @@ -24,6 +24,10 @@ import wordsData from "./words-data.js"; const words = wordsData; const wordSet = makeWordSet(words); +// All replies use HTML parse mode so renderGuess/renderBoard
 blocks
+// render as a monospace code box; emoji markers then align with letter columns.
+const HTML = { parse_mode: "HTML" };
+
 /**
  * @param {import("grammy").Context} ctx
  * @returns {number|null}
@@ -86,8 +90,8 @@ export async function handleWordle(ctx, db) {
       ? `๐ŸŽ‰ Solved in ${game.guesses.length}/${MAX_GUESSES}. /wordle_new for another.`
       : game.giveup
         ? `๐Ÿณ๏ธ Gave up. Answer was ${game.target.toUpperCase()}. /wordle_new for another.`
-        : `Guess ${game.guesses.length}/${MAX_GUESSES}. Use \`/wordle \`.`;
-    return ctx.reply(`${header}\n\n${renderBoard(game.guesses)}`);
+        : `Guess ${game.guesses.length}/${MAX_GUESSES}. Use /wordle <word>.`;
+    return ctx.reply(`${header}\n\n${renderBoard(game.guesses)}`, HTML);
   }
 
   if (isFinished(game)) {
@@ -110,15 +114,17 @@ export async function handleWordle(ctx, db) {
     const s = await recordResult(db, subject, true);
     return ctx.reply(
       `${reply}\n\n๐ŸŽ‰ Solved in ${game.guesses.length}/${MAX_GUESSES}! Streak: ${s.streak}. /wordle_new for another.`,
+      HTML,
     );
   }
   if (game.guesses.length >= MAX_GUESSES) {
     await recordResult(db, subject, false);
     return ctx.reply(
       `${reply}\n\nโŒ Out of guesses. Answer was ${game.target.toUpperCase()}. /wordle_new to retry.`,
+      HTML,
     );
   }
-  return ctx.reply(`${reply}\n\nGuess ${game.guesses.length}/${MAX_GUESSES}.`);
+  return ctx.reply(`${reply}\n\nGuess ${game.guesses.length}/${MAX_GUESSES}.`, HTML);
 }
 
 /**
@@ -137,7 +143,10 @@ export async function handleNew(ctx, db) {
   }
 
   await startFreshGame(db, subject);
-  return ctx.reply(`${prelude}๐Ÿ†• New round started. Use \`/wordle \` to guess.`);
+  return ctx.reply(
+    `${prelude}๐Ÿ†• New round started. Use /wordle <word> to guess.`,
+    HTML,
+  );
 }
 
 /**
diff --git a/src/modules/wordle/render.js b/src/modules/wordle/render.js
index 4125f62..3678fbf 100644
--- a/src/modules/wordle/render.js
+++ b/src/modules/wordle/render.js
@@ -1,28 +1,40 @@
 /**
- * @file Render wordle comparison results as a Telegram-friendly grid.
+ * @file Render wordle comparison results as a Telegram monospace grid.
+ *
+ * Each guess renders as two lines โ€” colored markers over the upper-case
+ * letters โ€” wrapped in an HTML 
 block so Telegram renders it in
+ * monospace and the emoji column widths line up with the letter columns.
+ *
  *   ๐ŸŸฉ correct ยท ๐ŸŸจ partial ยท โฌœ wrong
  *
- * Each guess shows two lines: the colored markers and the upper-case letters
- * underneath so the player can read the word at a glance.
+ * Output strings are intended to be sent with `parse_mode: "HTML"`. All
+ * game content is validated `[a-z]` so no HTML escaping is needed inside
+ * the grid; only callers embedding user-controlled text around the grid
+ * need to escape it.
  */
 
 const MARKER = { correct: "๐ŸŸฉ", partial: "๐ŸŸจ", wrong: "โฌœ" };
 
-/**
- * Render a single guess row (two lines: markers + letters).
- * @param {ReturnType} results
- */
-export function renderGuess(results) {
+function rowPair(results) {
   const markers = results.map((r) => MARKER[r.result] ?? "โฌœ").join("");
   const letters = results.map((r) => ` ${r.letter.toUpperCase()} `).join("");
   return `${markers}\n${letters}`;
 }
 
 /**
- * Render all prior guesses stacked.
+ * Render a single guess row as an HTML 
 block.
+ * @param {ReturnType} results
+ */
+export function renderGuess(results) {
+  return `
${rowPair(results)}
`; +} + +/** + * Render the full board (all prior guesses, blank-line separated) as a + * single HTML
 block, so all rows share one monospace code box.
  * @param {Array<{word:string, results: any[]}>} guesses
  */
 export function renderBoard(guesses) {
-  if (guesses.length === 0) return "No guesses yet. Reply with `/wordle `.";
-  return guesses.map((g) => renderGuess(g.results)).join("\n\n");
+  if (guesses.length === 0) return "No guesses yet. Reply with /wordle <word>.";
+  return `
${guesses.map((g) => rowPair(g.results)).join("\n\n")}
`; }