phase: 182_verbatim_show_text
Build and Push Containers / build-and-push-app (push) Successful in 1m46s
Build and Push Containers / build-and-push-db (push) Successful in 12s

**Phase 182 — final verification pass (all 8 tasks already complete; this was verification + docs + one defect fix).**

- Verified: `app/rag/verbatim.py` (locator grammar, `splitlines()` slice, line-boundary cap, `resolve_answer_blocks`), `SHOW_TEXT_TOOL` + `shown_texts`, `SHOW_TEXT_CLAUSE`, `done.answer_text` (9 fields, key dropped when empty) + `ChatMessageRecord.answer_text` (≤50, 600 000-char cap counted in the 8 M budget), `verbatim.js` + 4 `{verbatim:true}` renderers, `Containerfile` minify step, `.env.example` 3 vars.
- `uv run python -c "…answer_text, show_text_max_chars, show_text_max_blocks"` → `True 12000 5`; each negative refuses startup naming its field; `0` accepted as inert ✅
- `bash .agents/validate.sh` → **validation OK**: 4320 unit+integration tests green, `TOTAL 6849 17 → 99%` (gate app/ >90% OK), `ruff check .` "All checks passed!", `pyright` 0 errors (re-run after my edits, 08:28).
- E2E, each in isolation `--no-cov`: `test_verbatim_show_text.py` 13 passed (panel bytes == `documents.content`, server caption, clipboard == server string, `/document.html?source=…&path=…`, honest unresolved state, reload+share, `show_image` regression, flag-off app) ✅; green: `test_answer_images` `test_harness_aligned_tools` `test_agent_document_tools` `test_markdown_tables` `test_markdown_inline_links_images` `test_svg_document_preview` `test_smoke` `test_document_viewer` `test_markdown_blockquotes` `test_chat_persistence` `test_thinking_timeline` `test_chat_rag` `test_settings_mobile_value_width`.
- **Defect found & fixed (pre-existing, not phase 182):** `tests/e2e/test_admin_settings_panel.py::test_admin_sees_groups_and_source_badges` failed — its whole-page `assert "reese" not in page.content()` tripped on the shell's onboarding chips, which follow the newest saved chats in the one shared E2E DB (a leftover `reeselink.json` question from a tool suite). Scoped the strict sweep to `.settings-shell` and kept a whole-page `reese:reese` credential needle → 12 passed with the leftovers still present.
- Docs: README fixed 2 inconsistencies — Quick Start omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the app fail-loud refuses boot) and the broken anchor `#chat--` → `#chat-`; full re-read of README (1816 lines) + AGENTS confirmed the phase-182 feature/settings/log/UI-contract text matches the code, and every `.env.example` var + every `Settings` field is documented.
- Notable: kept `resolve_answer_blocks` synchronous on the SSE terminal path (bounded indexed SELECTs, matching the existing `query_log` precedent and the phase's documented fence-lookup bound); no live-infra changes made.
- Next pending phase: none — `todo/` holds only 182 (its 8 task files are in `complete/`; the harness commits + moves `00_phase.md`).
This commit is contained in:
2026-10-15 08:33:12 -04:00
parent 814a400e6d
commit b34b8ca672
104 changed files with 10852 additions and 203 deletions
+867
View File
@@ -0,0 +1,867 @@
"""Unit: the verbatim panel client and the answer's Copy buttons (phase 182, task 07).
The browser half of the story lives in the phase's Playwright suite (task
08); this module pins the SOURCE contract of ``frontend/assets/verbatim.js``
and the three client surfaces that consume it, in the established
``test_frontend_*`` convention — plus a node-driven half (skipped when node
is unavailable, the phase-44/138/180/182 idiom) that runs the panel's PURE
logic (caption, gutter, locator match, block guard) against the real file.
Why these particular pins (the phase's drift risks, in order):
* **ONE locator grammar.** ``markdown.js::borLocator`` is the client's single
normalizer (task 06); hydration may only compare the server's canonical
``locator`` against the already-normalized ``data-bor-locator`` with a
plain string ``===``. A second grammar in a second file is the phase's main
way to break silently — so this module pins that no normalizer exists in
``verbatim.js`` (no ``borLocator``, no range regex, no ``parseInt``).
* **The model's bytes never reach the screen or the clipboard.** The panel's
text node is ``pre.textContent = block.text`` (server bytes, verbatim) and
the Copy button copies the same string — never the DOM, never the gutter,
never the model's re-typed fence body (which task 06 dropped upstream).
* **The claim is carried by words, not colours** (LOCKED A5): the badge text
is ``VERBATIM``, the honest state is the pinned sentence ``I could not
verify this against the knowledge base``, and an ordinary code block gets a
Copy button with NO badge.
* **Parity.** The live turn, the reload restore and the anonymous shared page
call the SAME globals, in the SAME order (hydrate → append the unlocated →
settle the code blocks, all inside the ``answer_image`` band and BEFORE the
source clusters), so one answer cannot render three different ways.
* **No panel is ever left lying.** Every terminal path — the ``error`` frame,
the 300 s guard, the Stop action, the failed-turn funnel — settles the
leftover placeholders; nothing reads "Retrieving the exact text…" after the
turn is over.
* **CSP + the house XSS rule.** Everything is ``createElement`` +
``textContent``, every handler is ``addEventListener``, and there is no
``innerHTML``/``onclick=``/``oncopy=`` anywhere in the new client.
"""
from __future__ import annotations
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
ASSETS = FRONTEND / "assets"
VERBATIM_JS = ASSETS / "verbatim.js"
APP_JS = ASSETS / "app.js"
SHARED_JS = ASSETS / "shared.js"
DOCUMENT_JS = ASSETS / "document.js"
MARKDOWN_JS = ASSETS / "markdown.js"
STYLES_CSS = ASSETS / "styles.css"
INDEX_HTML = FRONTEND / "index.html"
SHARED_HTML = FRONTEND / "shared.html"
CONTAINERFILE = FRONTEND.parent / "Containerfile"
HAVE_NODE = shutil.which("node") is not None
#: The labelled phase-182 CSS region (task 06's markers — task 07 extends
#: INSIDE it, so the palette-only and contiguity pins keep holding).
BLOCK_BEGIN = "/* ---------- Verbatim panels (phase 182, task 06) — BEGIN ----------"
BLOCK_END = "/* ---------- Verbatim panels (phase 182, task 06) — END ---------- */"
#: The honest state (pinned copy — task 07's Testing section).
UNRESOLVED_COPY = "I could not verify this against the knowledge base"
#: The badge's WORD (LOCKED A5) and the tooltip that explains it.
BADGE_TEXT = "VERBATIM"
BADGE_TITLE = "These bytes are copied from the knowledge base, not written by the model."
#: The Copy control's accessible name template — the FULL path, never a
#: clipped stand-in (the phase-156 clipped-label rule).
COPY_ARIA = "Copy ${shown} lines from ${identity}"
#: The globals task 07 exposes for the two page modules.
PUBLIC_GLOBALS = (
"buildVerbatimPanel",
"hydrateVerbatim",
"appendUnlocatedVerbatim",
"settleVerbatimPlaceholders",
"enhanceAnswerCodeBlocks",
"copyTextWithFallback",
)
def _text(path: Path) -> str:
assert path.is_file(), f"missing file: {path}"
return path.read_text(encoding="utf-8")
def _strip_comments(text: str) -> str:
"""Comments out — the CSP and literal pins below are about CODE. The
file's own header documents the very patterns it forbids (``onclick=``,
``javascript:``), and a prose-blind grep would flag the documentation of
the rule as the rule."""
text = re.sub(r"/\*[\s\S]*?\*/", "", text)
return re.sub(r"(?m)^\s*//.*$", "", text)
def _fn(js: str, name: str) -> str:
"""The source of a top-level (optionally ``async``) ``function <name>``."""
start = js.find(f"function {name}(")
assert start != -1, f"{name}() must exist"
return js[start : js.find("\n}\n", start) + 4]
def _body(js: str, name: str) -> str:
return _fn(js, name)
# ---------------------------------------------------------------------------
# the client ships, and ships in the right shape
# ---------------------------------------------------------------------------
def test_verbatim_js_is_a_classic_script_after_markdown_on_both_pages() -> None:
"""Both pages load it as a PLAIN ``<script>`` immediately AFTER
``markdown.js`` (whose placeholders it hydrates) and BEFORE their page
module (which calls it as a global). Absolute paths on ``shared.html``
(the nested ``/shared/<token>`` route breaks a relative ref), relative on
``index.html`` exactly like its neighbours. No ``type="module"`` — a
module would defer past the pages' boot and expose no globals."""
index = _text(INDEX_HTML)
shared = _text(SHARED_HTML)
assert '<script src="assets/verbatim.js"></script>' in index
assert '<script src="/assets/verbatim.js"></script>' in shared
assert index.index('src="assets/markdown.js"') < index.index('src="assets/verbatim.js"'), (
"verbatim.js hydrates markdown.js's placeholders — it loads after it"
)
assert index.index('src="assets/verbatim.js"') < index.index(
'type="module" src="/assets/app.js"'
), "the globals must exist before app.js evaluates"
assert shared.index('src="/assets/markdown.js"') < shared.index('src="/assets/verbatim.js"')
assert shared.index('src="/assets/verbatim.js"') < shared.index(
'type="module" src="/assets/shared.js"'
)
tag = next(line for line in index.splitlines() if "verbatim.js" in line and "<script" in line)
assert 'type="module"' not in tag, "a classic script — the globals are the API"
# The document viewer never loads it (a file's own ```bor fence is text).
document_html = _text(FRONTEND / "document.html")
assert "verbatim.js" not in document_html
def test_containerfile_produces_it_as_a_minified_classic_script() -> None:
"""Stage 1 builds it the way it builds ``markdown.js``: ``--minify``
WITHOUT ``--bundle`` (bundling a classic script would break the global
exposure), and the source itself stays free of top-level
``import``/``export``."""
container = _text(CONTAINERFILE)
line = next(
row for row in container.splitlines() if "assets/verbatim.js" in row
)
assert "--minify" in line
assert "--outfile=/out/assets/verbatim.js" in line
assert "--bundle" not in line, "a classic script is never bundled"
assert "--format=esm" not in line
js = _text(VERBATIM_JS)
assert "\nimport " not in js and "\nexport " not in js
def test_every_public_global_exists() -> None:
"""The API the two page modules call, as top-level function declarations
(the ``markdown.js`` exposure pattern — a classic script's top-level
functions ARE the globals)."""
js = _text(VERBATIM_JS)
for name in PUBLIC_GLOBALS:
assert f"function {name}(" in js, f"verbatim.js must expose {name}"
def test_no_locator_normalizer_is_re_implemented() -> None:
"""THE phase's drift pin: hydration compares the server's canonical
``locator`` against the ``data-bor-locator`` markdown.js already
normalized, with a PLAIN string compare. No grammar here — no
``borLocator``, no range regex, no en-dash handling, no ``parseInt``, no
``L``-prefix stripping (a second grammar in a second file is how a
resolved block stops hydrating)."""
js = _text(VERBATIM_JS)
assert "function borLocator" not in js
assert "parseInt" not in js
assert "\u2013" not in _fn(js, "matchVerbatimBlock"), "no en-dash parsing here"
for pattern in ("[Ll]", "L?\\d", "#L", "trimStart", "normalize("):
assert pattern not in js, f"{pattern} looks like a re-implemented grammar"
match = _fn(js, "matchVerbatimBlock")
assert "block.locator === key" in match, "the exact match is a plain string compare"
hydrate = _fn(js, "hydrateVerbatim")
assert 'node.getAttribute("data-bor-locator")' in hydrate, (
"hydration reads the attribute markdown.js wrote — it never re-derives it"
)
# ---------------------------------------------------------------------------
# the panel's contract (LOCKED A2/A5/A7)
# ---------------------------------------------------------------------------
def test_panel_is_a_labelled_group_keyed_by_the_servers_locator() -> None:
"""``role="group"`` + ``aria-label="Verbatim text from <path>, lines
N\u2013M"`` + ``data-bor-locator`` = the SERVER's canonical locator (the
key ``appendUnlocatedVerbatim`` later recognises, which is why a
re-render never duplicates a panel)."""
panel = _fn(_text(VERBATIM_JS), "buildVerbatimPanel")
assert 'root.setAttribute("role", "group")' in panel
assert 'root.setAttribute("aria-label", verbatimPanelLabel(block))' in panel
assert "root.dataset.borLocator" in panel
assert "block.locator" in panel, "the server's locator is the panel's identity"
label = _fn(_text(VERBATIM_JS), "verbatimPanelLabel")
assert '"Verbatim text from' in json.dumps(label) or "Verbatim text from" in label
def test_badge_is_a_word_with_the_pinned_explanation() -> None:
"""LOCKED A5: the claim is the WORD ``VERBATIM`` (never an icon or a
colour), with the tooltip that says what it means."""
js = _text(VERBATIM_JS)
assert f'badge.textContent = "{BADGE_TEXT}"' in js
assert BADGE_TITLE in js
assert 'badge.className = "verbatim-badge"' in js
def test_unresolved_state_is_the_pinned_sentence_and_nothing_else() -> None:
"""The honest state for a locator the server did not resolve: the pinned
sentence, plus the document link ONLY when the identity parsed into a
``source/path``. NO badge, NO body, NONE of the model's bytes — and the
builder creates neither a badge nor a ``<pre>`` node at all, which is
stronger than hiding one."""
js = _text(VERBATIM_JS)
assert f'"{UNRESOLVED_COPY}"' in js
node = _fn(js, "buildVerbatimUnresolved")
assert 'root.className = "verbatim verbatim-unresolved"' in node
assert 'state.className = "verbatim-state"' in node
assert "verbatim-badge" not in node, "an unresolved panel never badges"
assert 'createElement("pre")' not in node, "and never has a body"
assert 'createElement("ol")' not in node, "nor a gutter"
assert "indexOf(" in node and "slash > 0" in node, (
"the link appears only when the identity split into a source and a path"
)
def test_copy_button_names_the_full_path_and_copies_the_servers_bytes() -> None:
"""The completion criterion at source level: the panel's Copy control is
built from ``block.text`` (never from the DOM, never with the gutter's
digits), and its accessible name is ``Copy N lines from <source/path>`` —
the full path, because the visible label is the CSS clip target
(phase 156)."""
js = _text(VERBATIM_JS)
panel = _fn(js, "buildVerbatimPanel")
assert COPY_ARIA in panel
build = panel[panel.index("buildCopyButton(") :]
assert '"verbatim-copy"' in build
assert "block.text," in build.split("\n)")[0], (
"the copied string is `block.text` — the exact bytes the panel shows"
)
control = _fn(js, "buildCopyButton")
assert 'btn.setAttribute("aria-label", ariaLabel)' in control
assert 'label.textContent = "Copy"' in control
assert 'label.textContent = "Copied"' in control, "the state is TEXT (WCAG 1.4.1)"
assert 'btn.classList.add("copied")' in control
clipboard = _fn(js, "copyTextWithFallback")
assert "await navigator.clipboard.writeText(text)" in clipboard, (
"the caller's string goes to the clipboard untouched — no DOM read"
)
def test_body_is_the_servers_text_with_the_servers_line_numbers() -> None:
"""The bounded body: the ``<ol>`` gutter (``aria-hidden`` — the range
rides the panel's label, and the digits are ``user-select: none`` in CSS
so a selection never drags them into the clipboard) and the ``<pre>``
whose ``textContent`` is ``block.text``, byte for byte."""
js = _text(VERBATIM_JS)
panel = _fn(js, "buildVerbatimPanel")
assert 'body.className = "verbatim-body"' in panel
assert 'numbers.className = "verbatim-lines"' in panel
assert 'numbers.setAttribute("aria-hidden", "true")' in panel
assert 'pre.className = "verbatim-text"' in panel
assert "pre.textContent = block.text" in panel
assert "li.textContent = row.number" in panel
gutter = _fn(js, "verbatimGutter")
assert 'split("\\n")' in gutter, "one gutter row per line of the SERVER text"
def test_truncated_block_says_the_rest_is_in_the_document() -> None:
"""A capped block appends ONE muted line plus the same viewer link — the
panel never pretends the slice is the whole file."""
panel = _fn(_text(VERBATIM_JS), "buildVerbatimPanel")
assert "if (block.truncated)" in panel
assert 'more.className = "verbatim-more"' in panel
assert "the rest is in the document" in panel
assert "buildVerbatimOpenLink(" in panel.split("if (block.truncated)")[1]
def test_open_link_is_a_real_href_with_the_modal_hook() -> None:
"""The panel links to the document viewer: an always-same-origin
``/document.html?source=…&path=…`` built with ``encodeURIComponent`` (so a
path can never turn into ``javascript:``/``data:``), plus the chat page's
click-to-modal hook through ``opts.onOpen`` — attached with
``addEventListener``, never an inline attribute."""
js = _text(VERBATIM_JS)
link = _fn(js, "buildVerbatimOpenLink")
assert 'link.className = "verbatim-open"' in link
assert "link.addEventListener(" in link
assert "event.preventDefault()" in link
assert "opts.onOpen(source, path, link)" in link
href = _fn(js, "verbatimDocumentHref")
assert "/document.html?source=" in href
assert "encodeURIComponent(source)" in href and "encodeURIComponent(path)" in href
code = _strip_comments(js)
assert "javascript:" not in code and "data:" not in code, (
"no scheme is ever assembled into a link target"
)
def test_plain_answer_code_blocks_get_copy_and_never_a_badge() -> None:
"""``enhanceAnswerCodeBlocks``: every ``<pre>`` in an answer bubble that
is NOT inside a ``.verbatim`` gets ``.code-block`` + ``.code-block-copy``.
NO badge and NO caption — verbatim-and-badged OR plainly the model's,
never a hybrid (LOCKED A5). Idempotent: a ``<pre>`` already inside a
``.code-block`` is left alone."""
fn = _fn(_text(VERBATIM_JS), "enhanceAnswerCodeBlocks")
assert 'box.className = "code-block"' in fn
assert '"code-block-copy"' in fn
assert 'pre.closest(".verbatim")' in fn, "a panel owns its own Copy button"
assert 'parent.classList.contains("code-block")' in fn, "idempotent on re-render"
assert "verbatim-badge" not in fn
assert "verbatim-caption" not in fn
def test_hydration_is_idempotent_and_never_throws_on_garbage() -> None:
"""Only ``.verbatim-pending`` is replaced, so a hydrated panel (or one
restored from storage) survives a re-run with its ``Copied`` state and
scroll position; a missing/malformed ``blocks`` settles to the honest
state instead of throwing."""
js = _text(VERBATIM_JS)
hydrate = _fn(js, "hydrateVerbatim")
assert ".verbatim-pending[data-bor-locator]" in hydrate
assert (
"verbatim-unresolved"
not in hydrate.split("matchVerbatimBlock")[1].split("replaceWith")[1].split("\n")[0]
), "the unresolved node comes from its own builder"
assert "replaceWith(" in hydrate
assert "try {" not in hydrate, "the guards make it total — no exception path"
assert "verbatimBlocks(blocks)" in hydrate
guard = _fn(js, "verbatimBlocks")
assert "Array.isArray(blocks)" in guard, "a missing frame key is not an error"
def test_unlocated_blocks_land_in_the_answer_image_band() -> None:
"""The blocks no placeholder matched (a ``show_text`` call the prose never
named) append to ``.msg-body`` — the same band as the answer image, below
the text and above the source clusters, in frame order — and a block
already placed (a panel carrying its canonical locator) is never
appended twice."""
fn = _fn(_text(VERBATIM_JS), "appendUnlocatedVerbatim")
assert 'wrap.querySelector(".msg-body")' in fn
assert "host.appendChild(buildVerbatimPanel(block, opts))" in fn
assert "placed.add(" in fn and "if (placed.has(key)) continue" in fn
assert "verbatim-unresolved" in fn, (
"an unresolved placeholder is NOT a placement (the block still belongs on screen)"
)
def test_terminal_settle_renders_the_honest_state() -> None:
"""``settleVerbatimPlaceholders`` turns every leftover ``.verbatim-pending``
into the unresolved node and records the ``kind`` for the next reader."""
js = _text(VERBATIM_JS)
fn = _fn(js, "settleVerbatimPlaceholders")
assert 'root.querySelectorAll(".verbatim-pending")' in fn
assert "buildVerbatimUnresolved(" in fn
assert "replaceWith(" in fn, "the pending node is REPLACED, never patched"
assert 'node.getAttribute("data-bor-locator")' in fn, (
"the locator the model named rides into the honest state"
)
unresolved = _fn(js, "buildVerbatimUnresolved")
assert 'root.dataset.verbatimState = (opts && opts.kind) || "unverified"' in unresolved, (
"the settle kind is recorded for the next reader"
)
assert "state.textContent = verbatimUnresolvedCopy()" in unresolved
# ---------------------------------------------------------------------------
# CSP + the house XSS rule (the new client, and the new code in the pages)
# ---------------------------------------------------------------------------
def test_no_inline_handlers_and_no_html_anywhere_in_the_client() -> None:
"""CSP is ``default-src 'self'``: no ``onclick=``/``oncopy=`` attribute
strings, no inline ``<script>``, no HTML string, no ``innerHTML`` (not
even ``+=``) in ``verbatim.js`` — every node is ``createElement``, every
value ``textContent``/``title``/a built ``href``, every handler
``addEventListener``."""
code = _strip_comments(_text(VERBATIM_JS))
for marker in (
"onclick=",
"oncopy=",
"onerror=",
"onload=",
".innerHTML",
".outerHTML",
"insertAdjacentHTML(",
"document.write(",
"eval(",
"<script",
):
assert marker not in code, f"verbatim.js must stay HTML/handler-free: {marker}"
assert "document.createElement(" in code
assert code.count("document.createElement(") > 15, (
"the panel is built node by node (the house rule)"
)
assert "addEventListener(" in code
def test_the_pages_new_code_adds_no_inline_handler_or_inner_html() -> None:
"""The task-07 additions to ``app.js`` / ``shared.js`` obey the same rule:
no ``innerHTML +=`` anywhere, and the only ``onclick=``-shaped text in the
two files stays inside their existing comments (none)."""
for path in (APP_JS, SHARED_JS):
js = _text(path)
assert "innerHTML += " not in js, f"{path.name}: no innerHTML concatenation"
assert "onclick=" not in js
assert "oncopy=" not in js
# ---------------------------------------------------------------------------
# app.js: the live turn, the save point, the restore, the terminal paths
# ---------------------------------------------------------------------------
def _done_branch(js: str) -> str:
start = js.index('ev.type === "done"')
end = js.index('ev.type === "tool_result"', start)
return js[start:end]
def test_the_done_handler_hydrates_before_the_source_clusters() -> None:
"""The order IS the layout: the answer image, then the hydrated panels,
then the blocks the answer never located, then the Copy buttons on the
settled code blocks, then the source clusters (phase-124/130 DOM order —
the panels sit below the answer text and above the chips)."""
branch = _done_branch(_text(APP_JS))
for call in (
"appendAnswerImage(wrap, ev.answer_image)",
"hydrateVerbatim(",
"appendUnlocatedVerbatim(wrap, ev.answer_text || [], VERBATIM_LINK_OPTS)",
"enhanceAnswerCodeBlocks(wrap)",
"appendSources(wrap, ev.sources)",
):
assert call in branch, f"the done handler must call {call}"
order = [
branch.index("appendAnswerImage("),
branch.index("hydrateVerbatim("),
branch.index("appendUnlocatedVerbatim("),
branch.index("enhanceAnswerCodeBlocks("),
branch.index("appendSources("),
]
assert order == sorted(order), "image → panels → unlocated → copy → sources"
assert "VERBATIM_LINK_OPTS" in branch[branch.index("hydrateVerbatim(") :][:200]
def test_the_answer_renderers_opt_in_and_nothing_else_does() -> None:
"""The per-delta write and the ``done``-time re-render both pass
``{ verbatim: true }`` (the third site is the restore renderer, pinned in
``test_the_restore_path_rehydrates_from_the_record``); the user bubble of
the same turn does NOT — a question is not a locator."""
js = _text(APP_JS)
branch = _done_branch(js)
assert "renderMarkdown(acc, { verbatim: true })" in js
assert "renderMarkdown(finalText, { verbatim: true })" in branch
assert 'addMessage(\n "user",\n renderMarkdown(text),' in js, (
"the sent question stays one-argument"
)
def test_the_save_point_persists_the_blocks_additively() -> None:
"""``answer_text: ev.answer_text || undefined`` on the ``done`` save point
— ``undefined`` drops the key from the JSON (the ``answer_image``
precedent), so a pre-phase record stays byte-identical and a
``answer_text=False`` turn stores nothing new."""
branch = _done_branch(_text(APP_JS))
assert "answer_text: ev.answer_text || undefined," in branch
turn = _fn(_text(APP_JS), "rememberBrainTurn")
assert "...meta" in turn, (
"the meta spread carries the key — rememberBrainTurn itself is untouched"
)
def test_the_restore_path_rehydrates_from_the_record() -> None:
"""Reload parity: the restore renderer opts in, then hydrates from
``m.answer_text`` and appends the unlocated blocks in the same band, then
settles the code blocks — the SAME builders as the live turn. A record
with no ``answer_text`` renders its fences as the unresolved state (the
record is what it is: never resurrect model bytes)."""
js = _text(APP_JS)
body = _fn(js, "renderStoredMessage")
assert 'addMessage("brain", renderMarkdown(m.text, { verbatim: true }), true)' in body
assert (
'hydrateVerbatim(wrap.querySelector(".bubble"), m.answer_text || [], VERBATIM_LINK_OPTS)'
in body
)
assert "appendUnlocatedVerbatim(wrap, m.answer_text || [], VERBATIM_LINK_OPTS)" in body
assert "enhanceAnswerCodeBlocks(wrap)" in body
order = [
body.index("appendAnswerImage(wrap, m.answer_image)"),
body.index("hydrateVerbatim("),
body.index("appendUnlocatedVerbatim("),
body.index("enhanceAnswerCodeBlocks("),
body.index("appendSources(wrap, m.sources)"),
]
assert order == sorted(order)
def test_every_terminal_path_settles_the_placeholders() -> None:
"""Nothing sits at \u201cRetrieving the exact text\u2026\u201d after a terminal
frame: the ``error`` frame handler, the 300 s guard, that guard's own
abort surface, the user-stop branch and the failed-turn funnel each call
``settleVerbatimPlaceholders(…, "unverified")`` — five sites, no more."""
js = _text(APP_JS)
assert js.count("settleVerbatimPlaceholders(") == 5, (
"the five terminal sites: error frame, timeout, aborted catch, stop "
"catch, failed-turn funnel"
)
assert js.count('"unverified"') >= 5, "every settle names the honest kind"
funnel = _fn(js, "finalizeFailedTurn")
assert 'settleVerbatimPlaceholders(wrap.querySelector(".bubble"), "unverified");' in funnel
assert funnel.index("closeAllRuns(wrap)") < funnel.index("settleVerbatimPlaceholders(")
stop = js[js.index("stoppedByUser || err?.name ===") :]
assert "settleVerbatimPlaceholders(" in stop[:800], "the Stop action settles"
frame = js[js.index('ev.type === "error"') :]
assert "settleVerbatimPlaceholders(" in frame[:700], "the error frame settles"
def test_the_chat_panel_links_back_to_the_chat_page() -> None:
"""The chat page's panels navigate EXACTLY like a source chip: the same
``documentUrl(…, "/")`` href (back → the chat, phase 153) and the same
left-click → same-page modal (phase 26), handed to the client as ONE
options object."""
js = _text(APP_JS)
opts = (
_fn(js, "VERBATIM_LINK_OPTS")
if "function VERBATIM_LINK_OPTS" in js
else js[
js.index("const VERBATIM_LINK_OPTS") : js.index(
"};", js.index("const VERBATIM_LINK_OPTS")
)
]
)
assert "documentHref:" in opts and 'documentUrl(source, path, "/")' in opts
assert "onOpen:" in opts and 'openDocumentModal(source, path, opener, "/")' in opts
# every app.js call site passes it
assert _text(APP_JS).count("VERBATIM_LINK_OPTS") >= 5
def test_the_share_path_delegates_to_the_one_clipboard_helper() -> None:
"""Task 07 consolidates the clipboard path: ``app.js``'s owner-locked
share copy delegates to ``copyTextWithFallback`` with the same host and
the same title, and the page no longer carries its own selection
helper."""
js = _text(APP_JS)
share = _fn(js, "copyShareLinkWithFallback")
assert "copyTextWithFallback(" in share
assert "composer," in share
assert '"Share link — click, then copy (Ctrl/Cmd+C)"' in share
assert "function selectAllInField" not in js, (
"the selection helper moved with the clipboard path — one implementation"
)
verbatim = _text(VERBATIM_JS)
assert "function selectAllInFallbackField" in verbatim
# ---------------------------------------------------------------------------
# shared.js parity
# ---------------------------------------------------------------------------
def test_shared_page_reaches_the_same_globals_with_a_plain_link() -> None:
"""Parity is a hard requirement: the shared renderer calls the SAME three
globals in the SAME band and order as the chat page's restore, and passes
a plain ``/document.html?source=&path=`` href with NO modal (that page has
none, and the documents API is admin-gated)."""
js = _text(SHARED_JS)
body = _fn(js, "renderSharedMessage")
assert "hydrateVerbatim(" in body
assert "appendUnlocatedVerbatim(wrap, m.answer_text || [], SHARED_VERBATIM_OPTS)" in body
assert "enhanceAnswerCodeBlocks(wrap)" in body
order = [
body.index("addAnswerImage(wrap, m.answer_image)"),
body.index("hydrateVerbatim("),
body.index("appendUnlocatedVerbatim("),
body.index("addSources(wrap, m.sources)"),
]
assert order == sorted(order)
opts = js[
js.index("const SHARED_VERBATIM_OPTS") : js.index(
"};", js.index("const SHARED_VERBATIM_OPTS")
)
]
assert "/document.html?source=" in opts and "encodeURIComponent(" in opts
assert "onOpen" not in opts, "no modal on the shared page"
assert "openDocumentModal" not in js
assert "document-modal" not in js
# ---------------------------------------------------------------------------
# styles.css: the states the JS adds stay inside the phase-182 block
# ---------------------------------------------------------------------------
def _css_block() -> str:
css = _text(STYLES_CSS)
start = css.index(BLOCK_BEGIN)
end = css.index(BLOCK_END, start)
return css[start : end + len(BLOCK_END)]
@pytest.mark.parametrize(
"selector", [".verbatim-body", ".verbatim-text", ".verbatim-open", ".verbatim-more"]
)
def test_the_task_07_dom_selectors_are_declared_in_the_phase_182_block(
selector: str,
) -> None:
"""The DOM the panel builder really produces is styled INSIDE the labelled
region (so task 06's palette-only and contiguity pins keep holding): the
body is the flex row the gutter's ``flex: 0 0 auto`` was written for, and
the viewer link + the truncation line are themed, not unstyled."""
block = _css_block()
rules = re.findall(r"(?m)^" + re.escape(selector) + r" \{([^}]*)\}", block)
assert rules, f"{selector} must be declared inside the phase-182 block"
# task 06's `_body` idiom: task 07 extends a rule with its own declaration
# line (a second block for the same selector), so every body counts.
body = " ".join(rules)
assert not re.search(r"#[0-9a-fA-F]{3,8}\b", body), "no hex colour"
for literal in ("rgb(", "rgba(", "hsl(", "hsla("):
assert literal not in body, f"{selector} must stay palette-only ({literal})"
assert not re.search(r":\s*(?:white|black|#fff|#000)\s*[;}]", body), (
f"{selector} must not name a colour"
)
if selector == ".verbatim-body":
assert "display: flex" in body, "the gutter + text row the sticky gutter needs"
if selector == ".verbatim-body":
assert "display: flex" in body, "the gutter + text row the sticky gutter needs"
if selector == ".verbatim-open":
assert "text-decoration: underline" in body, "a link is not colour-only"
assert "var(--ink-soft)" in body, "the link is themed, not unstyled"
if selector == ".verbatim-more":
assert "var(--ink-soft)" in body and "var(--line)" in body
def test_the_viewer_link_reaches_the_mobile_touch_floor() -> None:
"""PLAN §7's 44px tap target applies to the panel's link too — it lives in
the ``max-width: 640px`` block beside the two Copy controls."""
css = _text(STYLES_CSS)
mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}", css)
assert mobile, "the 640px block must exist"
hit = re.search(r"\.verbatim-open \{([^}]*)\}", mobile.group(1))
assert hit and "min-height: 44px" in hit.group(1)
# ---------------------------------------------------------------------------
# node: the panel's pure logic, run against the real file
# ---------------------------------------------------------------------------
_HARNESS = """
const fs = require("fs");
const src = fs.readFileSync(process.argv[2], "utf8");
const api = new Function(
src +
"\\nreturn { verbatimCaptionText, verbatimGutter, verbatimPanelLabel," +
" verbatimIdentity, verbatimSplitLocator, verbatimDocumentHref," +
" isVerbatimBlock, verbatimBlocks, matchVerbatimBlock, verbatimUnresolvedCopy," +
" verbatimBadgeTitle };"
)();
const cases = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
const out = {};
for (const [name, body] of Object.entries(cases)) {
try {
// `with` so the case bodies read the panel API as plain names (the file
// itself is sloppy-mode classic-script code, and the harness is its
// own scope — no strict-mode conflict).
const fn = new Function("api", "with (api) { return (" + body + "); }");
out[name] = { ok: true, value: fn(api) };
} catch (err) {
out[name] = { ok: false, error: String(err && err.message ? err.message : err) };
}
}
process.stdout.write(JSON.stringify(out));
"""
@pytest.fixture(scope="module")
def tmp_dir(tmp_path_factory: pytest.TempPathFactory) -> Path:
return tmp_path_factory.mktemp("verbatim")
def _node(cases: dict[str, str], tmp_dir: Path) -> dict[str, object]:
"""Run *cases* (JS bodies receiving the panel API) under node against the
real ``verbatim.js`` — the pure half needs no DOM."""
script = tmp_dir / "harness.cjs"
script.write_text(_HARNESS, encoding="utf-8")
case_file = tmp_dir / "cases.json"
case_file.write_text(
# the case bodies are plain JS EXPRESSIONS evaluated with the panel
# API in scope (the harness wraps them, see _HARNESS)
json.dumps(dict(cases)),
encoding="utf-8",
)
proc = subprocess.run(
["node", str(script), str(VERBATIM_JS), str(case_file)],
capture_output=True,
text=True,
timeout=60,
check=False,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_caption_prints_the_servers_extent(tmp_dir: Path) -> None:
"""The caption is the SERVER's resolved extent (LOCKED A2 made visible):
a slice reads ``path · lines 12\u201340 of 210``, a whole document reads
``path · all 210 lines``, a CAPPED block is never labelled "all" even when
it starts at line 1, and a garbage count degrades to the bare path rather
than printing a lie."""
out = _node(
{
"slice": """verbatimCaptionText({ source: "homelab", path: "a.md",
start_line: 12, end_line: 40, total_lines: 210 })""",
"whole": """verbatimCaptionText({ source: "homelab", path: "a.md",
start_line: 1, end_line: 210, total_lines: 210 })""",
"capped": """verbatimCaptionText({ source: "homelab", path: "a.md",
start_line: 1, end_line: 30, total_lines: 210, truncated: true })""",
"no_total": """verbatimCaptionText({ source: "homelab", path: "a.md",
start_line: 3, end_line: 4 })""",
"garbage": """verbatimCaptionText({ source: "homelab", path: "a.md",
start_line: "x", end_line: null })""",
"label": """verbatimPanelLabel({ source: "homelab", path: "a.md",
start_line: 12, end_line: 40 })""",
"label_whole": """verbatimPanelLabel({ source: "homelab", path: "a.md",
start_line: 1, end_line: 1 })""",
"href": """verbatimDocumentHref("src a", "dir/x y.md")""",
},
tmp_dir,
)
assert out["slice"] == {"ok": True, "value": "homelab/a.md \u00b7 lines 12\u201340 of 210"}
assert out["whole"] == {"ok": True, "value": "homelab/a.md \u00b7 all 210 lines"}
assert out["capped"] == {"ok": True, "value": "homelab/a.md \u00b7 lines 1\u201330 of 210"}
assert out["no_total"] == {"ok": True, "value": "homelab/a.md \u00b7 lines 3\u20134"}
assert out["garbage"] == {"ok": True, "value": "homelab/a.md"}
assert out["label"] == {
"ok": True,
"value": "Verbatim text from homelab/a.md, lines 12\u201340",
}
assert out["label_whole"] == {
"ok": True,
"value": "Verbatim text from homelab/a.md, line 1",
}, "one line reads `line N`, never `lines 1–1`"
# SEC: the href is built from ENCODED halves (the `documentUrl` parity —
# the whole path is one query value, slashes included, and FastAPI decodes
# it back), and it is always a same-origin relative URL.
assert out["href"] == {
"ok": True,
"value": "/document.html?source=src%20a&path=dir%2Fx%20y.md",
}
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_gutter_numbers_the_servers_lines_and_not_the_marker(tmp_dir: Path) -> None:
"""One gutter row per line of the server text (so the rows align with the
``<pre>``), numbered from the server's ``start_line``. The truncation
marker the cap appended is a real line of the panel but NOT a line of the
file, so it gets a blank cell (an empty cell keeps the rows aligned \u2014 the
gutter never claims a coordinate the file does not have)."""
out = _node(
{
"range": """verbatimGutter({ text: "one\\ntwo\\nthree", start_line: 12,
end_line: 14 }).map(r => r.number).join("|")""",
"truncated": """verbatimGutter({ text: "one\\ntwo\\n\\u2026truncated\\u2026",
start_line: 1, end_line: 2, truncated: true })
.map(r => JSON.stringify(r.number)).join("|")""",
"unnumbered": """verbatimGutter({ text: "a\\nb" }).map(r => r.number).join("|")""",
},
tmp_dir,
)
assert out["range"] == {"ok": True, "value": "12|13|14"}
assert out["truncated"] == {"ok": True, "value": '"1"|"2"|" "'}
assert out["unnumbered"] == {"ok": True, "value": " | "}
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_match_rule_exact_then_unique_path_then_none(tmp_dir: Path) -> None:
"""The ONE hydration rule: the canonical locator first; else exactly ONE
block for the same ``source/path`` (the model's range was wrong \u2014 the panel
shows the server's extent); else null, INCLUDING the ambiguous case (two
blocks, same file, different ranges \u2014 never guess)."""
out = _node(
{
"exact": """(() => { const b = [
{ source: "h", path: "a.md", locator: "h/a.md#12-40" },
{ source: "h", path: "a.md", locator: "h/a.md#1-5" }];
return matchVerbatimBlock(b, "h/a.md#1-5").locator; })()""",
"path_only": """(() => { const b = [{ source: "h", path: "a.md",
locator: "h/a.md#1-9" }];
return matchVerbatimBlock(b, "h/a.md#99-120").locator; })()""",
"whole_doc_path_only": """(() => { const b = [{ source: "h", path: "a.md",
locator: "h/a.md" }];
return matchVerbatimBlock(b, "h/a.md").locator; })()""",
"ambiguous": """(() => { const b = [
{ source: "h", path: "a.md", locator: "h/a.md#1-9" },
{ source: "h", path: "a.md", locator: "h/a.md#20-29" }];
return matchVerbatimBlock(b, "h/a.md#7-8"); })()""",
"none": """matchVerbatimBlock([], "h/a.md#1-2")""",
"other_path": """matchVerbatimBlock([{ source: "h", path: "b.md",
locator: "h/b.md" }], "h/a.md")""",
"split": """JSON.stringify(verbatimSplitLocator("h/a.md#12-40"))""",
"split_no_range": """JSON.stringify(verbatimSplitLocator("h/a.md"))""",
},
tmp_dir,
)
assert out["exact"] == {"ok": True, "value": "h/a.md#1-5"}
assert out["path_only"] == {"ok": True, "value": "h/a.md#1-9"}
assert out["whole_doc_path_only"] == {"ok": True, "value": "h/a.md"}
assert out["ambiguous"] == {"ok": True, "value": None}
assert out["none"] == {"ok": True, "value": None}
assert out["other_path"] == {"ok": True, "value": None}
assert out["split"] == {
"ok": True,
"value": '{"identity":"h/a.md","hasRange":true}',
}
assert out["split_no_range"] == {
"ok": True,
"value": '{"identity":"h/a.md","hasRange":false}',
}
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_malformed_blocks_are_dropped_and_never_throw(tmp_dir: Path) -> None:
"""A malformed / missing ``answer_text`` is a NON-event: every odd shape
(null, a bare object, a block without text, a block without a path,
numbers-as-strings) is filtered out instead of throwing \u2014 hydration stays
honest for the placeholders instead of painting garbage."""
out = _node(
{
"undefined_blocks": """verbatimBlocks(undefined).length""",
"not_an_array": """verbatimBlocks({ length: 1, 0: { source: "h", path: "a.md",
text: "x" } }).length""",
"mixed": """verbatimBlocks([null, 7, "s", {}, { source: "", path: "a.md", text: "x" },
{ source: "h", path: "", text: "x" },
{ source: "h", path: "a.md" },
{ source: "h", path: "a.md", text: "" }]).length""",
"identity_with_empty_text": """isVerbatimBlock({ source: "h",
path: "a.md", text: "" })""",
"identity": """verbatimIdentity("homelab", "runbooks/a.md")""",
"unresolved_copy": """verbatimUnresolvedCopy()""",
"badge_title": """verbatimBadgeTitle()""",
},
tmp_dir,
)
assert out["undefined_blocks"] == {"ok": True, "value": 0}
assert out["not_an_array"] == {"ok": True, "value": 0}
# only the last entry survives: the others miss source/path/text.
assert out["mixed"] == {"ok": True, "value": 1}
assert out["identity_with_empty_text"] == {"ok": True, "value": True}
assert out["identity"] == {"ok": True, "value": "homelab/runbooks/a.md"}
assert out["unresolved_copy"] == {"ok": True, "value": UNRESOLVED_COPY}
assert out["badge_title"] == {"ok": True, "value": BADGE_TITLE}