diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index 22e0289..0c79079 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -76,7 +76,7 @@ async function removeWord(w) { try { wpPool = await delJSON('/api/admin/word/pool/' + encodeURIComponent(w)); - wpMsg = `Removed “${w}”. It won’t appear as an answer — restore it any time below.`; + wpMsg = `Removed “${w}” from future puzzles (today’s answer is already set). Restore it any time below.`; await refreshLookup(); } catch { /* ignore */ } } @@ -817,8 +817,9 @@ {:else if section === 'games'}

Daily Word pool

-

Look up a word and add it to the answer pool. Only real, 5- or 6-letter - words in the guess dictionary qualify, so the daily answer is always solvable.

+

Look up a word to add or remove it from the answer pool. Only real, 5- or 6-letter + words in the guess dictionary qualify, so the daily answer is always solvable. Removals take + effect for future puzzles and can be restored any time.

set[s def _pick_answer(conn: sqlite3.Connection, date: str, variant: str) -> str: pool = answer_pool(conn, variant) + if not pool: # safety net: removals must never empty the pool — fall back to curated + pool = sorted(_POOL.get(variant, [])) recent = _recent_answers(conn, variant, max(1, len(pool) // 2)) start = _seed(date, "word", variant) % len(pool) for i in range(len(pool)): diff --git a/tests/test_pool_admin.py b/tests/test_pool_admin.py index 80d4462..4c18e5d 100644 --- a/tests/test_pool_admin.py +++ b/tests/test_pool_admin.py @@ -85,6 +85,17 @@ def test_import_dedupes_and_validates(conn): assert new5[1] in games.answer_pool(conn, "5") +def test_picker_survives_empty_live_pool(conn): + """An overzealous admin could tombstone every answer in a variant. The live + pool then reads empty, but the daily picker must not divide-by-zero — it falls + back to the curated baseline rather than crashing.""" + for w in list(games._POOL["5"]): + games.remove_pool_word(conn, w) + assert games.answer_pool(conn, "5") == [] # live pool truly empty + ans = games._pick_answer(conn, "2026-06-11", "5") # must not raise + assert ans in set(games._POOL["5"]) # fell back to curated + + def test_import_relifts_removed_word(conn): w = _a_curated("5") games.remove_pool_word(conn, w)