Files
hermes-webui/tests/test_issue3571_saved_prompts.py
8d4ed1f90d Release v0.51.338 — Release LB (saved prompts library, #3571) (#3860)
* feat(composer): add saved prompts library with per-profile storage (#2732)

* fix(composer): move saved-prompts popup out of .composer-left to preserve DOM test (#2732)

* fix(composer): correct ARIA roles, add server-side prompt limits (#2732)

* fix(composer): surface save-prompt errors instead of silent success toast (#2732)

* Release v0.51.338 — Release LB (saved prompts library, #3571)

Composer saved-prompts library (@rodboev): bookmark button → popup of saved
prompts; click to insert, save current input, delete. Persists to
$HERMES_HOME/webui/saved_prompts.json with server-side caps (8000 chars / 200).

Maintainer work (per Nathan): conditions were (a) verify it actually works and
(b) hide on mobile. Both met:
- Live-verified load/save/delete all persist through the UI.
- Added mobile-hide (#btnSavedPrompts,.saved-prompts-popup display:none in the
  @media max-width:640px + 900px composer blocks). DOM-verified visible at 1280px,
  vision-confirmed absent from the composer at 390px.
- Added missing Polish (pl) i18n for the 5 saved_prompts_* keys (PR had en+others
  but not pl — failed locale-parity).
- Added tests/test_issue3571_saved_prompts.py (mobile-hide + caps + wiring guards).

Full suite green, ESLint/scope-undef CLEAN, Opus SHIP-safe (auth-gated, CSRF,
XSS-safe, sane caps), Codex SAFE-TO-SHIP.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-08 19:32:12 -07:00

72 lines
2.7 KiB
Python

"""#3571 saved-prompts library — structural + mobile-visibility guards.
The saved-prompts composer affordance is a desktop-only feature: per Nathan
(2026-06-09) it must be hidden on mobile (too much for the narrow composer).
These tests pin the mobile-hide rule and the core wiring so a future refactor
can't silently regress either.
"""
import re
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
def read(rel: str) -> str:
return (REPO / rel).read_text(encoding="utf-8")
def test_saved_prompts_button_hidden_on_mobile():
"""#btnSavedPrompts (and its popup) must be display:none inside a mobile
max-width:640px @media block — desktop-only affordance. Verified by walking
each media block and confirming the hide rule lives inside a 640px-max one."""
css = read("static/style.css")
found = False
for m in re.finditer(r"@media([^{]*)\{", css):
cond = m.group(1)
if "max-width" not in cond or "640px" not in cond:
continue
# Slice to this block's matching close brace.
depth = 0
i = m.end() - 1
end = len(css)
while i < len(css):
if css[i] == "{":
depth += 1
elif css[i] == "}":
depth -= 1
if depth == 0:
end = i
break
i += 1
block = css[m.end():end]
if re.search(r"#btnSavedPrompts[^{]*\{[^}]*display:\s*none", block):
found = True
break
assert found, (
"#btnSavedPrompts must be hidden on mobile inside a max-width:640px block "
"(desktop-only saved-prompts feature)"
)
def test_saved_prompts_backend_caps_present():
"""The POST /api/prompts route must cap text length and total count so
saved_prompts.json can't grow unbounded."""
routes = read("api/routes.py")
assert "text too long" in routes, "POST /api/prompts must cap text length"
assert re.search(r"len\(prompts\)\s*>=\s*\d+", routes), (
"POST /api/prompts must cap the total number of saved prompts"
)
def test_saved_prompts_core_wiring_present():
"""The composer must expose the saved-prompts toggle + popup and the
load/save/delete API calls."""
js = read("static/messages.js")
assert "toggleSavedPromptsPopup" in js
assert "insertSavedPromptIntoComposer" in js
assert re.search(r"api\('/api/prompts',\s*\{method:'POST'", js), "save wiring (POST) missing"
assert re.search(r"api\('/api/prompts',\s*\{method:'DELETE'", js), "delete wiring (DELETE) missing"
html = read("static/index.html")
assert 'id="btnSavedPrompts"' in html
assert 'id="savedPromptsPopup"' in html