Admin: Word Search theme authoring + tidy word-pool chips

* New "Word Search themes" panel in the Games tab: enter a theme name + words,
  with live validation (4–8 letters, alpha, deduped) and a count vs the 28 needed
  to fill all three sizes. An " Suggest a word" button asks the LLM for one
  fresh word that fits the theme. Save/edit/remove; authored themes join the daily
  fallback rotation alongside the curated ones (wordsearch_themes table). The
  system still handles word distribution across sizes + placement.
* Daily Word pool's added-word chips now scroll within a bounded area so the
  console stays tidy as the list grows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-11 13:36:07 -04:00
parent 61f575ba6d
commit f71e760847
5 changed files with 268 additions and 2 deletions
+21
View File
@@ -402,3 +402,24 @@ def test_client_error_telemetry(tmp_path, monkeypatch):
assert len(rows) == 1 and rows[0]["reason"] == "boot-timeout" and rows[0]["path"] == "/play"
assert rows[0]["user_agent"] # captured from the request header
assert tc.get("/api/admin/stats").json()["client_errors"]["today"] == 1
def test_wordsearch_theme_admin(tmp_path, monkeypatch):
app, api = _make(tmp_path, monkeypatch, admin_email="boss@x.com")
assert TestClient(app).get("/api/admin/wordsearch/themes").status_code == 401 # gated
tc = _signin(app, api, "boss@x.com")
w28 = ["table", "chair", "clock", "shelf", "couch", "pillow", "window", "carpet", "mirror", "candle",
"kettle", "drawer", "closet", "curtain", "cushion", "basket", "bottle", "towel", "broom", "ladder",
"stairs", "pantry", "blanket", "vase", "hallway", "doorway", "mantel", "hamper"]
# too few valid words → 400
assert tc.post("/api/admin/wordsearch/themes", json={"theme": "X", "words": ["cat", "dog"]}).status_code == 400
# save ok (>= 28); listed with the right count
res = tc.post("/api/admin/wordsearch/themes", json={"theme": "My House", "words": w28}).json()
assert res["saved"] and any(t["theme"] == "My House" and t["count"] == 28 for t in res["themes"])
tid = next(t["id"] for t in res["themes"] if t["theme"] == "My House")
# edit/update keeps the same id
upd = tc.post("/api/admin/wordsearch/themes", json={"theme": "House Stuff", "words": w28, "id": tid}).json()
assert any(t["id"] == tid and t["theme"] == "House Stuff" for t in upd["themes"])
# remove
left = tc.delete(f"/api/admin/wordsearch/themes/{tid}").json()
assert not any(t["id"] == tid for t in left)