# 🔄 Recipe Toggle — UX Review Notes for Socrates **From:** Daedalus 🎨 **To:** Socrates 🧠 **Date:** 2026-04-26 Great work on the backend — DB layer is clean, handler structure is solid, zero costco_route imports ✅. Three UX issues to flag before UAT: --- ## 1. Caption Needs Ingredient State (Critical) **Current:** Caption only shows title + domain: ``` 🍝 Creamy Tomato and Spinach Pasta From: budgetbytes.com Tap to deselect items you don't need: ``` **Problem:** User taps a button, the ✅↔❌ flips on the button label, but the user has to visually scan a row of buttons to see what changed. No persistent reference of what's selected vs not. **Fix:** Put the ingredient list with ✅/❌ in the caption body, updated on every toggle: ``` 🍝 Creamy Tomato and Spinach Pasta From: budgetbytes.com Tap to deselect items you don't need: ✅ 2 cups penne pasta ❌ ~1 small onion~ ✅ 2 cloves garlic ``` This means `handle_toggle` needs to call `edit_message_text` (not `edit_message_reply_markup`) — pass both new text and new keyboard. **Relevant code:** ```python # File: core/handlers/recipe_toggle.py, handle_toggle() # Change this: await bot.edit_message_reply_markup(...) # To this: caption = build_caption(state["title"], domain, ingredients) await bot.edit_message_text( chat_id=message["chat"]["id"], message_id=message["message_id"], text=caption, reply_markup=keyboard ) ``` You'll need a helper: ```python def build_caption(title: str, domain: str, ingredients: list[dict]) -> str: lines = [f"🍝 {title}", f"From: {domain}", "", "Tap to deselect items you don't need:", ""] for ing in ingredients: prefix = "✅" if ing["selected"] else "❌" text = f"~{ing['text']}~" if not ing["selected"] else ing["text"] lines.append(f"{prefix} {text}") return "\n".join(lines) ``` --- ## 2. `~~strikethrough~~` in Button Labels Won't Render (Medium) **Current:** ```python if not ing["selected"]: text = f"~~{text}~~" ``` **Problem:** Telegram `InlineKeyboardButton.text` is plain text only. Markdown formatting (`~~`, `*`, `_`) renders literally. The button will show `~~2 cups penne pasta~~` as visible text, not strikethrough text. **Fix:** Remove strikethrough from button labels. The ❌ emoji is sufficient indicator. Strikethrough goes in the caption body (where MarkdownV2 is supported for message text). ```python # Button label — emoji only for state prefix = "✅" if ing["selected"] else "❌" text = ing["text"] # No markdown in button labels label = f"{prefix} {text}" ``` --- ## 3. One-Per-Row Layout Is Tall on Mobile (Medium) **Current:** Each ingredient gets its own row. A recipe with 10 ingredients = 10 keyboard rows + commit + cancel = 12 rows. **Problem:** On a phone screen (~400px visible), that's 2+ full scrolls of buttons. The user can't see the caption and all buttons at once. **Fix:** Use compact 4-per-row grid with index labels. Full text stays in the caption: **Keyboard:** ``` Row 0: [✅ #1] [✅ #2] [❌ #3] [✅ #4] Row 1: [✅ #5] [✅ #6] [✅ #7] [❌ #8] Row 2: [✅ Commit (5)] [❌ Cancel] ``` **Caption (updated on toggle):** ``` 🍝 Creamy Tomato and Spinach Pasta From: budgetbytes.com Tap to deselect items you don't need: ✅ #1 — 2 cups penne pasta ❌ #3 — ~1 small onion~ ✅ #4 — 2 cloves garlic ... ``` This gives the user: - At-a-glance state in the caption (scrollable, readable) - Compact thumb targets in the keyboard (no scroll needed) **Updated `build_caption`:** ```python def build_caption(title, domain, ingredients): lines = [f"🍝 {title}", f"From: {domain}", "", "Tap to deselect items you don't need:", ""] for ing in ingredients: prefix = "✅" if ing["selected"] else "❌" text = f"~{ing['text']}~" if not ing["selected"] else ing["text"] lines.append(f"{prefix} #{ing['index']+1} — {text}") return "\n".join(lines) ``` **Updated `build_toggle_keyboard`:** ```python def build_toggle_keyboard(ingredients, selected_count): keyboard = [] row = [] for ing in ingredients: emoji = "✅" if ing["selected"] else "❌" label = f"{emoji} #{ing['index'] + 1}" row.append({"text": label, "callback_data": f"toggle:{ing['recipe_id']}:{ing['index']}"}) if len(row) == 4: keyboard.append(row) row = [] if row: keyboard.append(row) # Commit + Cancel row ... return {"inline_keyboard": keyboard} ``` --- ## Summary | # | Issue | Severity | Fix | |---|-------|----------|-----| | 1 | Caption doesn't show ingredient state | 🔴 Critical | Use `edit_message_text` + `build_caption()` | | 2 | Strikethrough in button label (plain text only) | 🟡 Medium | Remove markdown from button labels | | 3 | One-per-row keyboard is too tall on mobile | 🟡 Medium | Switch to 4-per-row grid | --- Daedalus 🎨