"""Unit: the phase-59 task-06 doc edit screen (``/doc-edit.html``). No Python logic exists for this task — the behavior lives in ``frontend/doc-edit.html`` + ``frontend/assets/doc-edit.js`` + ``styles.css``, and it is E2E-gated by the story suite (task 07). Like the other frontend-adjacent unit files (``test_history_page.py``, ``test_save_as_doc_button.py``), this module pins the HTML/JS/CSS markers the edit loop depends on, so a silent regression is caught without a browser: * the house shell (AGENTS.md rule 5 + the login.html/shared.html minimal-flow-page lineage): skip-link, the SLIM header (brand + "Back to chat" — no nav), the full 72rem container (the phase-59 form-column cap is retired — owner instruction 2026-09-12), the ``container`` frame; * the form contract: ``#draft-title`` / ``#draft-path`` / ``#draft-body`` with visible labels, ``#push-doc-btn`` (the exact "Push to docs branch" copy) + the back link, ``#push-status`` (``role="status" aria-live="polite"``) and the hidden ``#push-error`` (``role="alert"``); * the admin gate — the ``sources-gate`` pattern, ship-hidden, with the no-JS ``?next=`` fallback (the page is static; the API is the authority — the draft endpoints are admin-only regardless); * the JS: the whoami gate (anonymous branch makes NO ``/api/doc-drafts`` call), the token handling (missing → "No draft specified.", non-uuid → "Draft not found." with no fetch), the three API paths (GET draft / PUT edits / POST push — the PUT runs BEFORE the push: the endpoint commits the row, so unsaved edits would push stale text), the §7.4 never-stale lifecycle (disable + "Pushing…", re-enable in the finally), the success line (``Pushed to — commit .``), the failure banner (git's stderr trimmed to its first meaningful lines, fields preserved), and VALUES-not-innerHTML everywhere. The Containerfile stage-1 coverage (doc-edit.html copied, doc-edit.js bundled) and the cache-busting registration (``/doc-edit.html`` in ``HTML_PAGES``) are pinned by ``test_containerfile_assets.py`` / ``test_caching.py``. """ from __future__ import annotations import re from pathlib import Path FRONTEND = Path(__file__).resolve().parents[2] / "frontend" ASSETS = FRONTEND / "assets" DOC_EDIT_HTML = FRONTEND / "doc-edit.html" DOC_EDIT_JS = ASSETS / "doc-edit.js" STYLES_CSS = ASSETS / "styles.css" def _html() -> str: assert DOC_EDIT_HTML.is_file(), "frontend/doc-edit.html is missing" return DOC_EDIT_HTML.read_text(encoding="utf-8") def _js() -> str: assert DOC_EDIT_JS.is_file(), "frontend/assets/doc-edit.js is missing" return DOC_EDIT_JS.read_text(encoding="utf-8") def _css() -> str: return STYLES_CSS.read_text(encoding="utf-8") def _fn(js: str, name: str) -> str: """The source of a top-level ``function (...)`` (to its close).""" start = js.find(f"function {name}(") assert start != -1, f"{name}() must exist in doc-edit.js" return js[start : js.find("\n}\n", start) + 4] # ---------- the house shell (AGENTS.md rule 5) ---------- def test_page_scaffold_slim_header_and_landmarks() -> None: """The minimal-flow-page scaffold (the login.html/shared.html lineage): skip-link, the SLIM header (brand + the "Back to chat" link to / — and NO nav: this is a flow page, not one of the app's pages), ``
`` with the ``container`` frame, and the house footer.""" html = _html() assert '' in html assert 'class="app-header"' in html # The slim header: the brand + the back link. assert 'Brain of Reese' in html back = re.search(r']*class="doc-edit-back"[^>]*href="/"[^>]*>', html) assert back, "the header must carry the 'Back to chat' link to /" assert "Back to chat" in html # And NO nav — the flow-page lineage (no hamburger, no app-nav). assert 'id="app-nav"' not in html, "the slim header ships no nav" assert 'id="nav-toggle"' not in html, "the slim header ships no hamburger" assert "
" in html assert '
' in html assert 'class="app-footer"' in html def test_page_title_and_description() -> None: """The page's identity: the house title shape ( · Brain of Reese) + a description naming the admin-only flow.""" html = _html() assert "Edit doc · Brain of Reese" in html desc = re.search(r' None: """The three fields (task 06): #draft-title (text), #draft-path (text), #draft-body (textarea) — each with a VISIBLE ``