"""Unit: phase-66 text pins — the History tab describes the auto-save model, not the retired Save button. House pattern (``test_stale_ui_copy.py``): read ``frontend/history.html`` as text and assert substrings — no browser. The browser-visible layer is gated by the dedicated story suite (``tests/e2e/test_history_copy.py``); these pins catch a silent regression in the template without it. Locked decision (owner-locked A3, 2026-09-01): every conversation saves itself automatically — there is NO Save button (retired phase 55, owner- locked A2, pinned by ``test_save_share_ux.py::test_anonymous_auto_save``), so no user-visible History-tab string may name one. """ from __future__ import annotations import re from pathlib import Path FRONTEND = Path(__file__).resolve().parents[2] / "frontend" # --- the locked auto-save copy (owner-locked A3) ----------------------- META = "Saved chats — every conversation is saved automatically, one click back." PAGE_SUB = ( "Every conversation is saved automatically — newest activity first. " "Click a title to return to that chat." ) EMPTY_ROW = "No saved chats yet — start a conversation and it will be saved automatically." # --- the retired manual-save copy (must be nowhere) -------------------- OLD_META = "every conversation you saved" OLD_PAGE_SUB = "you pressed Save" OLD_EMPTY_ROW = "press Save in the chat" # --- state-language survivors (must STAY — "saved" is a state, not a # button; their absence would prove over-deletion) ------------------ STATE_STRINGS = ( "

Saved chats

", "Sign in to view your saved chats", "Saved conversations are admin-only", ) def _text() -> str: return (FRONTEND / "history.html").read_text(encoding="utf-8") def _norm(text: str) -> str: """Collapse whitespace runs — the templates wrap long lines, so the locked copy is pinned against the normalized text.""" return re.sub(r"\s+", " ", text).strip() def test_manual_save_copy_is_gone() -> None: """history.html: the three retired manual-save strings are GONE — the copy no longer tells the visitor to press a Save button.""" html = _text() for frag in (OLD_META, OLD_PAGE_SUB, OLD_EMPTY_ROW): assert frag not in html, f"retired manual-save copy still present: {frag!r}" def test_locked_auto_save_copy_present_exactly_once() -> None: """The three locked (A3) auto-save strings, each exactly once in history.html (a second copy could drift out of sync).""" html = _norm(_text()) assert html.count(META) == 1, "the locked meta description" assert html.count(PAGE_SUB) == 1, "the locked page-sub" assert html.count(EMPTY_ROW) == 1, "the locked empty-row string" def test_state_language_survivors_are_untouched() -> None: """Proof of NO over-deletion: the strings where "saved" is a state — the

, the anonymous gate title, and the gate sub — stay exactly as phase 50 wrote them.""" html = _text() for frag in STATE_STRINGS: assert frag in html, f"state-language survivor deleted: {frag!r}"