Files
brain-of-reese/tests/unit/test_frontend_verbatim_blocks.py
T
ducoterra fb17d15fbe
Build and Push Containers / build-and-push-app (push) Successful in 1m56s
Build and Push Containers / build-and-push-db (push) Successful in 14s
phase: 192_verbatim_inline_midturn
Phase 192 — final verification pass complete.

- **Verified (all 4 tasks already complete):** additive `verbatim_block` SSE frame (`VerbatimBlockPiece` → `ChatVerbatimBlockEvent` → chat dispatch, after the round's `tool`/`tool_result`, before the next round); `hydrateVerbatimNow` matched-only mid-turn pass + app.js branch; `dropPlacedTwin` per-locator done/restore dedupe; the dedicated E2E suite.
- **Root cause of the rejected attempt:** my own concurrent background `pytest` run deadlocked the harness validation run on the shared Postgres (`psycopg.errors.DeadlockDetected`). No code defect — re-ran every gate **exclusively**.
- `uv run pytest -q --cov=app --cov-report=term-missing` → **exit 0**, `TOTAL 7181 16 99%` (>90%)
- `uv run pytest tests/e2e/test_verbatim_inline_midturn.py --no-cov` → **6 passed** (isolated)
- `uv run pytest tests/e2e/test_verbatim_show_text.py tests/e2e/test_chat_history.py --no-cov` → **18 passed** (`test_chat_history.py` byte-unedited; the phase-182 suite's "no eighth type" pin was tightened, not loosened, to seven + `verbatim_block` with frame-order asserts)
- `uv run ruff check .` → All checks passed (exit 0) · `uv run pyright` → **0 errors**
- **Completion criteria:** single frame per success + zero per refusal ✅ · `BOR_ANSWER_TEXT=0` wire byte-identical (integration spelled baseline + E2E App B) ✅ · fence hydrates in place / unfenced lands at the call moment, before `done` ✅ · append+fence → 1 panel, two ranges → 2 panels ✅ · restore 1 panel/locator, `done.answer_text` + record key + `verbatim_blocks=N` unchanged ✅ · suite green, coverage 99%, lint/types clean ✅ · no-show_text turn byte-identical ✅ · commit → left to the harness (no git index/history commands run).
- **Docs:** README reviewed line-by-line (2399 lines) + AGENTS.md in full; mechanically verified all 86 `BOR_*` settings, every `.env.example` var, table defaults, internal anchors (0 broken), ports 8123/8901, tree depth 1–8, pagination 25/1–100, 17 palette colors, `0029`/`0030` migrations, and every cited test file. Fixed 1 inconsistency: the verbatim "Parity" bullet claimed the live turn/reload/shared page share "the same three globals" — now names the settle-path trio and states that `hydrateVerbatimNow` is the ONE chat-only addition (a shared page has no stream).
- **Notable:** `run_agent`'s four repeated piece unions were collapsed into the ONE exported `AgentPiece` alias, so a future piece type is spelled in one place; `test_frontend_edit_message.py`'s `runTurn` byte-pin was regenerated, not weakened.
- **Next pending phase:** `193_sync_remaining_count`.
2026-10-19 12:19:51 -04:00

1714 lines
78 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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.
* **The mid-turn pass settles ONLY what it matches** (phase 192, task 02):
the additive ``verbatim_block`` frame carries ONE block, and
``hydrateVerbatimNow`` may replace only the pending placeholders that block
matches — every other placeholder STAYS ``.verbatim-pending`` for the
``done`` / failure path, because burning a fence for a block that has not
arrived yet is how a resolved panel turns into "I could not verify this".
* **One canonical locator, one panel** (phase 192, A3): a mid-turn block that
no fence named yet is appended at the answer position, and when the answer
fences it later the hydration drops that appended twin (``dropPlacedTwin``,
bounded by the ``.msg`` wrap) — the scan is PER LOCATOR, so two honest
ranges of one file stay two panels.
* **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
from typing import Any
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",
"hydrateVerbatimNow", # phase 192 (task 02): the MID-TURN pass
"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)
def _doc(js: str, name: str) -> str:
"""The ``/* … */`` doc comment that sits directly above
``function <name>`` — the house docstring-pinning convention for the
clients: the prose IS the contract record, so a refactor that "simplifies"
the rule away has to delete the sentence in the same change."""
start = js.find(f"\nfunction {name}(")
assert start != -1, f"function {name}() must exist"
open_ = js.rfind("/*", 0, start)
close = js.find("*/", open_)
assert open_ != -1 and close != -1 and close < start, (
f"{name}() must carry the doc comment above it"
)
return js[open_ : close]
def _flow(text: str) -> str:
"""Comment prose as one line — the pins quote the SENTENCE, not its
wrapping (the `` * `` line leaders come out with the wrap)."""
lines = [re.sub(r"^\s*\*+\s?", " ", line) for line in text.splitlines()]
return " ".join(" ".join(lines).split())
# ---------------------------------------------------------------------------
# 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_midturn_hydration_settles_only_the_placeholders_one_block_matches() -> None:
"""Phase 192 (task 02, A2) — the heart of the mid-turn pass. The additive
``verbatim_block`` frame carries ONE block, and ``hydrateVerbatimNow``:
* walks the SAME pending set as the done pass
(``.verbatim-pending[data-bor-locator]``, the attribute ``markdown.js``
normalized — no second grammar);
* matches through the ONE rule (``matchVerbatimBlock`` with a one-block
list) so the mid-turn pass and the ``done`` pass can never disagree
about which fence this block owns;
* replaces ONLY the matched nodes (``buildVerbatimPanel``) — the unmatched
stay ``.verbatim-pending``, NEVER the honest unresolved state, which is
the done/failure path's job (``settleVerbatimPlaceholders``);
* returns the number settled (``0`` is the caller's signal to append);
* is total without a ``try`` — a missing root, a root that is not an
element, or a frame that is not a block all return ``0``."""
js = _text(VERBATIM_JS)
fn = _fn(js, "hydrateVerbatimNow")
assert '.verbatim-pending[data-bor-locator]"' in fn, (
"the mid-turn walk is the done path's pending set"
)
assert 'node.getAttribute("data-bor-locator")' in fn, (
"the key is the attribute markdown.js wrote — never re-derived"
)
assert "matchVerbatimBlock(" in fn, "the ONE match rule, not a mid-turn variant"
assert "const list = [block]" in fn, "the one-block frame rides the done list shape"
assert "replaceWith(buildVerbatimPanel(" in fn, "only a MATCH builds a panel"
assert "buildVerbatimUnresolved" not in fn, (
"an unmatched fence stays pending mid-turn — settling is the terminal path's job"
)
assert "if (!matched) return" in fn, "the unmatched node is left in place"
assert "settled += 1" in fn and "return settled" in fn, (
"the count is the caller's append/no-append signal"
)
assert 'typeof root.querySelectorAll !== "function"' in fn, "a missing root is 0"
assert "isVerbatimBlock(block)" in fn, "a malformed frame settles NOTHING"
assert "try {" not in fn, "the guards make it total — no exception path"
assert "innerHTML" not in fn
def test_hydration_drops_the_midturn_twin_and_never_an_unresolved_one() -> None:
"""Phase 192 (A3): a block that arrived mid-turn before its fence was
appended at the answer position; when its fence hydrates, that twin is
removed so ONE locator keeps ONE panel and the panel sits where the model
fenced it. The shape of the scan is the contract:
* bounded by the message wrap (the appended panel lives in ``.msg-body``,
beside the bubble — a bubble-local walk would miss it);
* ``.verbatim-pending`` and ``.verbatim-unresolved`` are NOT placements
(the ``appendUnlocatedVerbatim`` exclusion, shared);
* the key is the ONE ``verbatimPlacedKey`` derivation (the same one the
append-side scan reads — two derivations is how the dedupe drifts);
* it compares the canonical LOCATOR (never an identity — two ranges of one
file are two panels) and never removes the placeholder being hydrated."""
js = _text(VERBATIM_JS)
twin = _fn(js, "dropPlacedTwin")
assert 'host.querySelectorAll(".verbatim[data-bor-locator]")' in twin
assert 'el.classList.contains("verbatim-pending")' in twin
assert 'el.classList.contains("verbatim-unresolved")' in twin
assert 'el.getAttribute("data-bor-locator") !== placedKey' in twin, (
"PER LOCATOR — an identity scan would collapse two honest ranges"
)
assert "verbatimSplitLocator" not in twin and "verbatimIdentity(" not in twin, (
"no identity fallback in the twin scan"
)
assert "if (el === keep) return" in twin, "the hydrating placeholder is never the twin"
assert "el.remove()" in twin
hydrate = _fn(js, "hydrateVerbatim")
assert 'root.closest(".msg")' in hydrate, "the scan is bounded by the message wrap"
assert "if (block) dropPlacedTwin(host, block, node);" in hydrate, (
"only a MATCHED placeholder drops a twin"
)
# The ONE key derivation, read by both dedupe sides.
assert js.count("function verbatimPlacedKey(") == 1
assert "verbatimPlacedKey(block)" in _fn(js, "appendUnlocatedVerbatim")
#: The sentences the phase-192 (A3) prose must keep carrying. They are the
#: only place a reader of ``verbatim.js`` learns the dedupe's two rules, and
#: the fastest way to lose a rule is a refactor that drops its documentation
#: first and its code second.
TWIN_DOC_SENTENCES = (
# PER LOCATOR, never per identity — the two-ranges-stay clause.
"PER LOCATOR, never per identity: two different ranges of one file are"
" two honest panels",
# The in-text panel wins over the mid-turn appended twin.
"The IN-TEXT panel wins",
)
def test_the_twin_dedupe_docstring_keeps_the_per_locator_clause() -> None:
"""Phase 192 (task 03, A3): the prose is part of the contract. The
``dropPlacedTwin`` docstring names the per-LOCATOR rule (and the
two-honest-ranges clause that follows from it) and why the in-text panel
wins; ``hydrateVerbatim``'s docstring says the matched placeholder drops
that twin ("one locator, one panel, and it sits where the model fenced
it"); and the module header states the same rule for whoever reads the
file top-down."""
js = _text(VERBATIM_JS)
twin = _flow(_doc(js, "dropPlacedTwin"))
for sentence in TWIN_DOC_SENTENCES:
assert sentence in twin, f"the docstring lost: {sentence}"
assert "appendUnlocatedVerbatim" in twin, (
"the placement exclusion is documented as SHARED with the append side"
)
hydrate = _flow(_doc(js, "hydrateVerbatim"))
assert "dropPlacedTwin" in hydrate
assert "one locator, one panel, and it sits where the model fenced it" in hydrate
header = _flow(js[: js.find("*/")])
assert "ONE CANONICAL LOCATOR, ONE PANEL (phase 192)" in header
assert "two honest ranges of one file stay two panels" in header
def test_the_twin_scan_bounds_to_the_msg_wrap_or_the_root() -> None:
"""The scan's host is the message wrap (the appended twin lives in
``.msg-body``, beside the bubble), and when a root has no ``.msg``
ancestor the ROOT bounds it — a missing wrap narrows the scan, it never
switches the dedupe off."""
hydrate = _fn(_text(VERBATIM_JS), "hydrateVerbatim")
assert 'root.closest(".msg")' in hydrate
assert 'root.closest(".msg") : null) || root' in hydrate, (
"no .msg ancestor → the root bounds the scan"
)
assert "document.querySelectorAll" not in hydrate, (
"never a page-wide scan — one message's twin, one message's panels"
)
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 _midturn_branch(js: str) -> str:
start = js.index('ev.type === "verbatim_block"')
end = js.index('ev.type === "error"', start)
return js[start:end]
def test_the_midturn_frame_handler_hydrates_in_place_and_appends_only_on_zero() -> None:
"""Phase 192 (task 02, A2): the live turn's ``verbatim_block`` branch.
The shape IS the behaviour: hydrate the pending fence FIRST, and reach
for ``appendUnlocatedVerbatim`` ONLY on the zero-match path — a fence that
is already on screen keeps its panel where the model put it, and only a
block no placeholder names lands at the answer position (the
``answer_image`` band). The branch sits in the same ``readSSE`` chain, after
``tool_result`` (the server's emission order) and before ``error``.
And there is NO client-side kill switch: the server's
``BOR_ANSWER_TEXT`` gate is the only gate, so a disabled turn simply sends
no frame (a second, client-side settings check is a drift risk, exactly
the phase's reasoning for keeping the gate server-side)."""
js = _text(APP_JS)
branch = _midturn_branch(js)
assert "hydrateVerbatimNow(" in branch, "the mid-turn pass settles the fence"
assert "appendUnlocatedVerbatim(wrap, [ev], VERBATIM_LINK_OPTS)" in branch, (
"the unmatched block lands at the answer position, ONE opts object"
)
assert re.search(r"if \(\w+ === 0\)\s*appendUnlocatedVerbatim\(", branch), (
"the append is the ZERO-match path only — never alongside an in-place panel"
)
order = [branch.index("hydrateVerbatimNow("), branch.index("appendUnlocatedVerbatim(")]
assert order == sorted(order), "hydrate in place FIRST, append only if nothing matched"
assert "VERBATIM_LINK_OPTS" in branch[branch.index("hydrateVerbatimNow(") :][:200], (
"the mid-turn panel navigates exactly like the done panel (chip parity)"
)
# No second gate: nothing in the branch reads settings / a flag / the
# done frame's key — the arrival of the frame is the whole condition.
code = _strip_comments(branch)
for gate in ("answer_text", "answerText", "settings", "await ", "fetch("):
assert gate not in code, f"{gate} looks like a client-side gate (server-only)"
# The frame type is the ONE new thing the live turn knows (the server
# sends it between `tool_result` and the next round).
assert js.index('ev.type === "tool_result"') < js.index('ev.type === "verbatim_block"')
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}
# ---------------------------------------------------------------------------
# node: the MID-TURN pass (phase 192, task 02), run against the real file on
# a minimal DOM — the marker pins above prove the SHAPE, these prove the
# behaviour the E2E (task 04) will show in a browser: matched fences settle in
# place, everything else stays pending, a bad frame settles nothing.
# ---------------------------------------------------------------------------
#: The minimal DOM + the fixtures (``pendingBubble`` / ``shownBlock`` /
#: ``snapshot``). ``querySelectorAll`` understands the ONE compound selector
#: the client walks (``.a.b[attr]``) and throws on anything else, so a
#: selector drift in the client fails loudly instead of matching nothing.
_DOM_LIB = """
class El {
constructor(tag) {
this.tag = tag;
this.children = [];
this.parent = null;
this.attrs = {};
this.dataset = {};
this.classes = new Set();
this._own = "";
}
get className() { return [...this.classes].join(" "); }
set className(v) { this.classes = new Set(String(v).split(/\\s+/).filter(Boolean)); }
get classList() {
return { contains: (c) => this.classes.has(c), add: (c) => this.classes.add(c) };
}
setAttribute(k, v) { this.attrs[k] = String(v); }
addEventListener(ev, fn) { (this.listeners = this.listeners || {})[ev] = fn; }
getAttribute(k) {
if (Object.prototype.hasOwnProperty.call(this.attrs, k)) return this.attrs[k];
if (k.startsWith("data-")) {
const camel = k.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
if (Object.prototype.hasOwnProperty.call(this.dataset, camel)) {
return String(this.dataset[camel]);
}
}
return null;
}
appendChild(c) { c.parent = this; this.children.push(c); return c; }
append(...cs) { for (const c of cs) this.appendChild(c); }
insertBefore(box, ref) {
const i = this.children.indexOf(ref);
this.children.splice(i < 0 ? this.children.length : i, 0, box);
box.parent = this;
return box;
}
replaceWith(node) {
if (!this.parent) throw new Error("replaceWith on a detached node");
const i = this.parent.children.indexOf(this);
this.parent.children[i] = node;
node.parent = this.parent;
this.parent = null;
}
remove() {
if (!this.parent) return;
const i = this.parent.children.indexOf(this);
if (i >= 0) this.parent.children.splice(i, 1);
this.parent = null;
}
closest(sel) {
let cur = this;
while (cur) {
if (matches(cur, sel)) return cur;
cur = cur.parent;
}
return null;
}
querySelectorAll(sel) { return descendants(this).filter((el) => matches(el, sel)); }
querySelector(sel) { return descendants(this).find((el) => matches(el, sel)) || null; }
get textContent() {
if (!this.children.length) return this._own;
return this.children.map((c) => c.textContent).join("");
}
set textContent(v) { this.children = []; this._own = String(v); }
}
function descendants(el, out) {
const list = out || [];
for (const c of el.children) { list.push(c); descendants(c, list); }
return list;
}
function matches(el, sel) {
const parts = sel.match(/\\.[A-Za-z0-9_-]+|\\[[^[\\]]+\\]/g) || [];
if (!parts.length) throw new Error("unsupported selector: " + sel);
return parts.every((p) =>
p.startsWith(".")
? el.classes.has(p.slice(1))
: el.getAttribute(p.slice(1, -1)) !== null
);
}
globalThis.document = {
createElement: (t) => new El(t),
createTextNode: (t) => {
const node = new El("#text");
node._own = String(t);
return node;
},
querySelectorAll: () => [],
body: new El("body"),
};
function pendingBubble(locators) {
const bubble = new El("div");
bubble.classes.add("bubble");
for (const loc of locators) {
const node = new El("div");
node.className = "verbatim verbatim-pending";
node.dataset.borLocator = loc;
bubble.appendChild(node);
}
return bubble;
}
function shownBlock(over) {
return Object.assign({
source: "homelab",
path: "runbooks/a.md",
title: "Runbook A",
start_line: 12,
end_line: 40,
total_lines: 210,
text: "line twelve\\nline thirteen",
truncated: false,
locator: "homelab/runbooks/a.md#12-40",
}, over || {});
}
function pendingWrap(locators) {
const msg = new El("div");
msg.className = "msg brain";
const body = new El("div");
body.className = "msg-body";
const bubble = new El("div");
bubble.className = "bubble";
msg.appendChild(body);
body.appendChild(bubble);
for (const loc of locators) {
const node = new El("div");
node.className = "verbatim verbatim-pending";
node.dataset.borLocator = loc;
bubble.appendChild(node);
}
return msg;
}
function findClass(root, cls) {
return descendants(root).find((el) => el.classes.has(cls)) || null;
}
function inBubble(el) {
let cur = el.parent;
while (cur) {
if (cur.classes.has("bubble")) return true;
cur = cur.parent;
}
return false;
}
/* The RESOLVED panels in document order — a pending or unresolved node is
* NOT a placement (appendUnlocatedVerbatim's own convention), which is what
* makes this the "one locator, one panel" reading. */
function placed(msg) {
return descendants(msg)
.filter((el) => el.classes.has("verbatim"))
.filter((el) => !el.classes.has("verbatim-pending"))
.filter((el) => !el.classes.has("verbatim-unresolved"))
.map((el) => ({
locator: el.getAttribute("data-bor-locator"),
where: inBubble(el) ? "bubble" : "body",
}));
}
/* The fence the model writes in a LATER round (the mid-turn frame arrived
* before it): a fresh pending node inside the bubble. */
function addFence(msg, locator) {
const node = new El("div");
node.className = "verbatim verbatim-pending";
node.dataset.borLocator = locator;
msg.querySelector(".bubble").appendChild(node);
return node;
}
/* The same later-round fence inside a BARE bubble (no `.msg` wrap) — the arm
* that proves the ROOT bounds the twin scan when there is no message wrap. */
function bareFence(bubble, locator) {
const node = new El("div");
node.className = "verbatim verbatim-pending";
node.dataset.borLocator = locator;
bubble.appendChild(node);
return node;
}
function snapshot(root) {
return descendants(root)
.filter((el) => el.classes.has("verbatim"))
.map((el) => {
const caption = findClass(el, "verbatim-caption");
const pre = findClass(el, "verbatim-text");
const link = findClass(el, "verbatim-open");
return {
pending: el.classes.has("verbatim-pending"),
unresolved: el.classes.has("verbatim-unresolved"),
locator: el.getAttribute("data-bor-locator"),
caption: caption ? caption.textContent : null,
text: pre ? pre.textContent : null,
href: link ? link.href : null,
};
});
}
module.exports = {
El, pendingBubble, pendingWrap, shownBlock, snapshot, placed, addFence,
bareFence, findClass, descendants,
};
"""
_DOM_HARNESS = """
const fs = require("fs");
const src = fs.readFileSync(process.argv[2], "utf8");
const dom = require(process.argv[3]); // installs globalThis.document
const cases = JSON.parse(fs.readFileSync(process.argv[4], "utf8"));
const panel = new Function(
src +
"\\nreturn { hydrateVerbatimNow, hydrateVerbatim, appendUnlocatedVerbatim," +
" buildVerbatimPanel, isVerbatimBlock };"
)();
const scope = Object.assign({}, panel, dom);
const out = {};
for (const [name, body] of Object.entries(cases)) {
try {
const fn = new Function("api", "with (api) { return (" + body + "); }");
out[name] = { ok: true, value: fn(scope) };
} catch (err) {
out[name] = { ok: false, error: String(err && err.message ? err.message : err) };
}
}
process.stdout.write(JSON.stringify(out));
"""
#: The block a mid-turn ``verbatim_block`` frame carries in these cases.
SHOWN_BLOCK_LOCATOR = "homelab/runbooks/a.md#12-40"
#: The OTHER fence on screen mid-answer — the one this frame must NOT touch.
OTHER_PENDING_LOCATOR = "homelab/runbooks/b.md#1-5"
#: The caption the panel must carry in every arm: the SERVER's extent (the
#: phase-182 contract), never the range the model typed into its fence.
CAPTION_SERVER_EXTENT = "homelab/runbooks/a.md \u00b7 lines 12\u201340 of 210"
def _node_dom(cases: dict[str, str], tmp_dir: Path) -> dict[str, Any]:
"""Run *cases* (JS expressions with the panel API + the DOM fixtures in
scope) under node against the real ``verbatim.js`` on the minimal DOM.
``Any`` on purpose: the DOM cases return nested objects/arrays, and the
assertions below walk them (``value["nodes"][0]["caption"]``)."""
lib = tmp_dir / "bor_dom.cjs"
lib.write_text(_DOM_LIB, encoding="utf-8")
script = tmp_dir / "bor_dom_harness.cjs"
script.write_text(_DOM_HARNESS, encoding="utf-8")
case_file = tmp_dir / "dom_cases.json"
case_file.write_text(json.dumps(dict(cases)), encoding="utf-8")
proc = subprocess.run(
["node", str(script), str(VERBATIM_JS), str(lib), 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_midturn_pass_settles_the_matched_fence_and_leaves_the_rest(tmp_dir: Path) -> None:
"""The behaviour of ``hydrateVerbatimNow`` against the real file: the fence
whose canonical locator the frame carries settles IN PLACE with the
server's caption and bytes; the next fence in the same answer is NOT
touched (no panel, no unresolved sentence — it stays a promise the ``done``
frame still has to keep)."""
out = _node_dom(
{
"exact": f"""(() => {{
const bubble = pendingBubble(["{SHOWN_BLOCK_LOCATOR}", "{OTHER_PENDING_LOCATOR}"]);
const n = hydrateVerbatimNow(bubble, shownBlock(), {{}});
return {{ n, nodes: snapshot(bubble) }};
}})()""",
"wrong_range_uses_the_servers_extent": """(() => {
const bubble = pendingBubble(["homelab/runbooks/a.md#99-120"]);
const n = hydrateVerbatimNow(bubble, shownBlock(), {});
return { n, nodes: snapshot(bubble) };
})()""",
"two_fences_one_locator": f"""(() => {{
const bubble = pendingBubble(["{SHOWN_BLOCK_LOCATOR}", "{SHOWN_BLOCK_LOCATOR}"]);
const n = hydrateVerbatimNow(bubble, shownBlock(), {{}});
return {{ n, pending: snapshot(bubble).filter((x) => x.pending).length }};
}})()""",
"opts_thread_through": f"""(() => {{
const bubble = pendingBubble(["{SHOWN_BLOCK_LOCATOR}"]);
const n = hydrateVerbatimNow(bubble, shownBlock(), {{
documentHref: (s, p) => "HOOK/" + s + "/" + p,
}});
return {{ n, nodes: snapshot(bubble) }};
}})()""",
},
tmp_dir,
)
exact = out["exact"]
assert exact["ok"] is True, exact
assert exact["value"]["n"] == 1, "exactly the matching placeholder settles"
settled, untouched = exact["value"]["nodes"]
assert settled == {
"pending": False,
"unresolved": False,
"locator": SHOWN_BLOCK_LOCATOR,
"caption": CAPTION_SERVER_EXTENT,
"text": "line twelve\nline thirteen",
"href": "/document.html?source=homelab&path=runbooks%2Fa.md",
}, "the panel is the done path's panel, built from the SERVER's bytes"
assert untouched == {
"pending": True,
"unresolved": False,
"locator": OTHER_PENDING_LOCATOR,
"caption": None,
"text": None,
"href": None,
}, "a fence this block does not name STAYS pending"
# The model's range was wrong: the identity fallback still applies, and
# the caption / data attribute carry the SERVER's extent (mid-turn and
# done can never disagree — the phase-182 contract).
fallback = out["wrong_range_uses_the_servers_extent"]
assert fallback["ok"] is True, fallback
assert fallback["value"]["n"] == 1
assert fallback["value"]["nodes"][0]["locator"] == SHOWN_BLOCK_LOCATOR
assert fallback["value"]["nodes"][0]["caption"] == CAPTION_SERVER_EXTENT
dupes = out["two_fences_one_locator"]
assert dupes["ok"] is True, dupes
assert dupes["value"]["n"] == 2 and dupes["value"]["pending"] == 0
hooked = out["opts_thread_through"]
assert hooked["ok"] is True, hooked
assert hooked["value"]["n"] == 1
assert hooked["value"]["nodes"][0]["href"] == "HOOK/homelab/runbooks/a.md", (
"opts (the chat's modal hook) reach the mid-turn panel"
)
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_midturn_pass_is_total_on_garbage_and_no_match(tmp_dir: Path) -> None:
"""Nothing a mid-turn frame can carry settles a fence the block does not
name \u2014 and nothing throws: no match, a malformed frame (the ``isVerbatimBlock``
guard), a null root, a root that is not an element, no root at all. The
pending node stays ``.verbatim-pending`` in every arm (NEVER the honest
unresolved sentence \u2014 that is the terminal path's verdict, not this one)."""
out = _node_dom(
{
"other_file": f"""(() => {{
const bubble = pendingBubble(["{OTHER_PENDING_LOCATOR}"]);
const n = hydrateVerbatimNow(bubble, shownBlock(), {{}});
return {{ n, nodes: snapshot(bubble) }};
}})()""",
"malformed_no_source": f"""(() => {{
const bubble = pendingBubble(["{SHOWN_BLOCK_LOCATOR}"]);
const n = hydrateVerbatimNow(bubble, {{ path: "runbooks/a.md", text: "x" }}, {{}});
return {{ n, nodes: snapshot(bubble) }};
}})()""",
"malformed_not_an_object": f"""(() => {{
const bubble = pendingBubble(["{SHOWN_BLOCK_LOCATOR}"]);
const n = hydrateVerbatimNow(bubble, "verbatim", {{}});
return {{ n, nodes: snapshot(bubble) }};
}})()""",
"null_root": "hydrateVerbatimNow(null, shownBlock(), {})",
"plain_object_root": "hydrateVerbatimNow({}, shownBlock(), {})",
"undefined_root": "hydrateVerbatimNow(undefined, shownBlock(), undefined)",
},
tmp_dir,
)
for name in ("other_file", "malformed_no_source", "malformed_not_an_object"):
case = out[name]
assert case["ok"] is True, (name, case)
assert case["value"]["n"] == 0, name
node = case["value"]["nodes"][0]
assert node["pending"] is True and node["unresolved"] is False, (
f"{name}: the fence stays a promise, never a burned placeholder"
)
assert node["text"] is None, f"{name}: no panel, so no bytes on screen"
for name in ("null_root", "plain_object_root", "undefined_root"):
assert out[name] == {"ok": True, "value": 0}, name
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_midturn_panel_survives_the_done_pass(tmp_dir: Path) -> None:
"""The parity that keeps the phase additive: the mid-turn pass runs, then
the ``done`` frame arrives with the SAME block. ``hydrateVerbatim`` only
touches ``.verbatim-pending``, so the panel the mid-turn pass built is the
SAME node afterwards (its ``Copied`` state and scroll position survive),
and the unrelated fence settles to the honest sentence."""
out = _node_dom(
{
"then_done": f"""(() => {{
const bubble = pendingBubble(["{SHOWN_BLOCK_LOCATOR}", "{OTHER_PENDING_LOCATOR}"]);
const n1 = hydrateVerbatimNow(bubble, shownBlock(), {{}});
const midturnPanel = findClass(bubble, "verbatim");
const n2 = hydrateVerbatim(bubble, [shownBlock()], {{}});
const nodes = snapshot(bubble);
return {{
n1,
n2,
sameNode: findClass(bubble, "verbatim") === midturnPanel,
nodes,
}};
}})()""",
},
tmp_dir,
)
case = out["then_done"]
assert case["ok"] is True, case
value = case["value"]
assert value["n1"] == 1 and value["n2"] == 1
assert value["sameNode"] is True, (
"the done pass re-runs over the mid-turn panel without rebuilding it"
)
settled, unresolved = value["nodes"]
assert settled["pending"] is False and settled["locator"] == SHOWN_BLOCK_LOCATOR
assert unresolved["pending"] is False and unresolved["unresolved"] is True, (
"the fence no frame ever resolved lands on the honest state at done"
)
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_one_locator_one_panel_across_the_midturn_and_done_passes(tmp_dir: Path) -> None:
"""The duplicate the mid-turn landing would otherwise leave behind (the
phase's A3): a ``show_text`` block arrives BEFORE the answer fences it, so
the mid-turn pass appends it at the answer position; the fence lands in a
later round and hydrates at ``done``. Exactly ONE panel remains and it sits
IN THE BUBBLE, where the model put the fence.
The two arms that must NOT collapse: two different ranges of one file are
two honest panels (the scan is per canonical locator), and a block the
answer never fences keeps its single appended panel."""
out = _node_dom(
{
"fence_arrives_later": """(() => {
const msg = pendingWrap([]);
const bubble = msg.querySelector(".bubble");
const mid = hydrateVerbatimNow(bubble, shownBlock(), {});
const appended = appendUnlocatedVerbatim(msg, [shownBlock()], {});
addFence(msg, "homelab/runbooks/a.md#99-120");
const settled = hydrateVerbatim(bubble, [shownBlock()], {});
return { mid, appended, settled, panels: placed(msg) };
})()""",
"two_ranges_one_file": """(() => {
const b1 = shownBlock();
const b2 = shownBlock({ start_line: 10, end_line: 14,
locator: "homelab/runbooks/a.md#10-14" });
const msg = pendingWrap([]);
const bubble = msg.querySelector(".bubble");
appendUnlocatedVerbatim(msg, [b1], {});
addFence(msg, "homelab/runbooks/a.md#10-14");
addFence(msg, "homelab/runbooks/a.md#12-40");
hydrateVerbatim(bubble, [b1, b2], {});
return { panels: placed(msg) };
})()""",
"never_fenced_keeps_its_panel": """(() => {
const msg = pendingWrap([]);
const bubble = msg.querySelector(".bubble");
const appended = appendUnlocatedVerbatim(msg, [shownBlock()], {});
const settled = hydrateVerbatim(bubble, [shownBlock()], {});
const again = appendUnlocatedVerbatim(msg, [shownBlock()], {});
return { appended, settled, again, panels: placed(msg) };
})()""",
"repeated_fence_shows_the_bytes_once": """(() => {
const msg = pendingWrap(["homelab/runbooks/a.md#12-40",
"homelab/runbooks/a.md#12-40"]);
const bubble = msg.querySelector(".bubble");
const settled = hydrateVerbatim(bubble, [shownBlock()], {});
return { settled, panels: placed(msg) };
})()""",
"unresolved_twin_is_not_a_placement": """(() => {
const msg = pendingWrap(["homelab/runbooks/a.md#1-2"]);
const bubble = msg.querySelector(".bubble");
hydrateVerbatim(bubble, [], {}); // the honest state, same locator
const appended = appendUnlocatedVerbatim(msg, [shownBlock()], {});
return { appended, panels: placed(msg) };
})()""",
},
tmp_dir,
)
fenced = out["fence_arrives_later"]
assert fenced["ok"] is True, fenced
assert fenced["value"] == {
"mid": 0,
"appended": 1,
"settled": 1,
"panels": [{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"}],
}, "the fence wins: one panel, in the answer, the mid-turn twin gone"
ranges = out["two_ranges_one_file"]
assert ranges["ok"] is True, ranges
assert ranges["value"]["panels"] == [
{"locator": "homelab/runbooks/a.md#10-14", "where": "bubble"},
{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"},
], "two ranges of one file are TWO panels — the scan is per locator"
unfenced = out["never_fenced_keeps_its_panel"]
assert unfenced["ok"] is True, unfenced
assert unfenced["value"] == {
"appended": 1,
"settled": 0,
"again": 0,
"panels": [{"locator": SHOWN_BLOCK_LOCATOR, "where": "body"}],
}, "a block no fence names keeps exactly its one answer-position panel"
repeated = out["repeated_fence_shows_the_bytes_once"]
assert repeated["ok"] is True, repeated
assert repeated["value"]["settled"] == 2
assert repeated["value"]["panels"] == [
{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"}
], "the same fence twice shows those bytes once"
unresolved = out["unresolved_twin_is_not_a_placement"]
assert unresolved["ok"] is True, unresolved
assert unresolved["value"]["appended"] == 1, (
"an unresolved node is not a placement, so the block still lands"
)
assert unresolved["value"]["panels"] == [
{"locator": SHOWN_BLOCK_LOCATOR, "where": "body"}
]
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_the_twin_scan_bounds_to_the_message_it_hydrates(tmp_dir: Path) -> None:
"""The dedupe's BOUND, run against the real file (task 03): the scan reads
the message the hydration belongs to.
* a bare bubble with no ``.msg`` ancestor still dedupes — the ``|| root``
fallback means "no wrap" narrows the scan to the root, it never turns
the dedupe off (two panels for one locator is the bug either way);
* a second range of the same file keeps its own panel under that fallback
too (per locator, never per identity — the fallback is not a wider scan);
* a twin that belongs to ANOTHER message survives: hydrating one answer
never reaches into the next one's panels."""
out = _node_dom(
{
"bare_bubble_dedupes_too": """(() => {
const bubble = pendingBubble([]);
const appended = appendUnlocatedVerbatim(bubble, [shownBlock()], {});
bareFence(bubble, "homelab/runbooks/a.md#12-40");
const settled = hydrateVerbatim(bubble, [shownBlock()], {});
return { appended, settled, panels: placed(bubble) };
})()""",
"bare_bubble_two_ranges_stay_two": """(() => {
const other = shownBlock({ start_line: 10, end_line: 14,
locator: "homelab/runbooks/a.md#10-14" });
const bubble = pendingBubble([]);
appendUnlocatedVerbatim(bubble, [shownBlock()], {});
bareFence(bubble, "homelab/runbooks/a.md#10-14");
const settled = hydrateVerbatim(bubble, [shownBlock(), other], {});
return { settled, panels: placed(bubble) };
})()""",
"another_message_is_out_of_bounds": """(() => {
const here = pendingWrap(["homelab/runbooks/a.md#12-40"]);
const neighbour = pendingWrap([]);
appendUnlocatedVerbatim(neighbour, [shownBlock()], {});
const settled = hydrateVerbatim(
here.querySelector(".bubble"), [shownBlock()], {});
return { settled, here: placed(here), neighbour: placed(neighbour) };
})()""",
},
tmp_dir,
)
bare = out["bare_bubble_dedupes_too"]
assert bare["ok"] is True, bare
assert bare["value"] == {
"appended": 1,
"settled": 1,
"panels": [{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"}],
}, "no .msg wrap still means ONE panel for one locator"
ranges = out["bare_bubble_two_ranges_stay_two"]
assert ranges["ok"] is True, ranges
assert ranges["value"]["settled"] == 1
assert ranges["value"]["panels"] == [
{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"},
{"locator": "homelab/runbooks/a.md#10-14", "where": "bubble"},
], "the fallback bounds the scan — it does not widen it past the locator"
neighbour = out["another_message_is_out_of_bounds"]
assert neighbour["ok"] is True, neighbour
assert neighbour["value"]["settled"] == 1
assert neighbour["value"]["here"] == [
{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"}
], "this answer hydrates to one panel"
assert neighbour["value"]["neighbour"] == [
{"locator": SHOWN_BLOCK_LOCATOR, "where": "body"}
], "the next answer's panel is not this scan's to remove"
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_a_stored_record_restores_one_panel_per_locator(tmp_dir: Path) -> None:
"""The restore/shared parity of the dedupe (task 03): both call sites run
the SAME two passes in the SAME order — hydrate the record's fences, then
append the blocks the answer never located. So a stored answer that repeats
one locator twice and additionally carries an unlocated block comes back as
exactly TWO panels: the locator once, IN the text, and the unlocated block
once, in the answer band. One reload, one render — the duplicate the
mid-turn frame could leave behind never reaches the record's reader."""
out = _node_dom(
{
"restore": f"""(() => {{
const msg = pendingWrap(["{SHOWN_BLOCK_LOCATOR}", "{SHOWN_BLOCK_LOCATOR}"]);
const bubble = msg.querySelector(".bubble");
const record = [
shownBlock(),
shownBlock({{
source: "homelab", path: "runbooks/c.md", title: "Runbook C",
start_line: 1, end_line: 3, total_lines: 9,
text: "c one\\nc two", locator: "homelab/runbooks/c.md#1-3",
}}),
];
const settled = hydrateVerbatim(bubble, record, {{}});
const appended = appendUnlocatedVerbatim(msg, record, {{}});
return {{ settled, appended, panels: placed(msg) }};
}})()""",
},
tmp_dir,
)
case = out["restore"]
assert case["ok"] is True, case
assert case["value"]["settled"] == 2, "every fence settles — nothing is left pending"
assert case["value"]["appended"] == 1, (
"only the block the answer never located lands in the answer band"
)
assert case["value"]["panels"] == [
{"locator": SHOWN_BLOCK_LOCATOR, "where": "bubble"},
{"locator": "homelab/runbooks/c.md#1-3", "where": "body"},
], "one locator, one panel — the same answer on every surface"