phase: 153_document_viewer_context
Build and Push Containers / build-and-push-app (push) Successful in 2m45s
Build and Push Containers / build-and-push-db (push) Successful in 19s

That last `--co` (collect-only) run was my probe mistake — it shows 34% because no tests execute under collect-only. The valid coverage run was the real gate command executed earlier: `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (exit 0, all 3544 tests executed). All gates are verified.

## Phase 153 — final verification pass report

**Verified (all tasks 01–04 complete in `complete/`):**
- Modal `fullPageUrl(source, path, back = null)` + `openDocumentModal(…, back = null)` with module-level `currentBack`; 5 chat call sites pass `"/"`, sources.js call site unchanged (byte-identical Sources path); `documentUrl` comment now true
- Copy-path control (shared `renderDocument` core, icon+text, text = accessible name) on both surfaces; per-page clipboard helpers with the fallback-field + status-line contract; `#doc-copy-announcer` live region; styles + 44px touch floor
- Unit pins: `&back=%2F` / no-`back` byte-identity, `currentBack` single-writer guard, copy strings byte-pinned, phase-13 regression pins untouched

**Test / lint / coverage results:**
- `uv run pytest` → **3544 passed**, 0 failed (full unit + integration)
- `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (>90% gate)
- `uv run pytest tests/e2e/test_document_viewer_context.py -v --no-cov` → **8 passed** in 47.33s, in isolation (DB up, mock LLM)
- `uv run ruff check . && uv run pyright` → clean (0 errors/warnings)

**Completion criteria:** all met — (1) chat→modal→Full page: `href="/"` + "Chat"; Sources→modal→Full page: `href="/sources.html"` + "Sources" with `back=` asserted absent; (2) no-`back` URLs byte-identical to pre-phase; (3) Copy path: "Path copied." on success (clipboard readback), prefilled select-on-focus fallback field + "Path ready — copy it from the field." on rejected clipboard, both surfaces parameterized; (4) copied string exactly `docs/homelab/kubernetes.md`; (5) full suite green, E2E isolated green, lint clean, coverage 99%; (6) commit by harness.

**Documentation:** README.md reviewed in full — phase-153 feature already named in the Chat section; routes, env reference, workflows, deployment all consistent — no changes needed. AGENTS.md reviewed in full — rules, gates, and quick-reference commands verified against the repo (incl. `test_smoke.py`, `.env.example`, gitignore entries) — consistent, no changes needed.

**Notable:** pytest 9.1.1 quirk — a doubled `-q` (pyproject `addopts` + explicit `-q`) swallows the final "N passed" summary line while still exiting 0; the plain `uv run pytest` gate command prints it. No defects found in prior work.

**Next pending phase:** `154_sync_status_freshness`.
This commit is contained in:
2026-10-04 09:23:54 -04:00
parent 12fa69b7ea
commit 966c4b0540
59 changed files with 4304 additions and 34 deletions
+790 -7
View File
@@ -48,6 +48,30 @@ site):
innerHTML/outerHTML (the module's ONE innerHTML survives), and the
``.doc-origin`` CSS mirroring the ``.doc-path`` ellipsis idiom with
the ``.doc-link`` affordance (+ the ≤640px squeeze).
Phase 153 (task 03) — unit pins for the URL contract and the
copy-path strings (additions beside the task-01/02 pins; nothing
loosened):
* the phase-13 back-resolution contract, guarded BOTH as a byte-for-byte
source pin of the resolution block AND as a node behavioral run of
that exact extracted block (missing / empty / garbage /
protocol-relative / pseudo-protocol ``back`` → ``/sources.html`` +
the "Sources" label; ``/`` → the "Chat" label; any other same-origin
relative → the "Back" label — the phase-13 pins themselves are
untouched);
* the copy-path composition end-to-end under node — the modal's
``wireCopyPath`` click handler and the page's ``render()`` wiring
each send the EXACT ``source + "/" + path`` bytes to the (fake)
clipboard for a source/path pair with spaces + slashes, and the
rejected-clipboard failure prefills the fallback field with that
same composed string (success / failure strings + the prefill,
byte-for-byte);
* the ``document.html`` Copy-path static nodes — the page's
``header.doc-header`` (the ``.doc-copy-pop`` block's anchor —
absent, the float would fall back to the meta row and grow the
bar, the phase-12/34 breach) + the ``#doc-copy-announcer``
(role=status) + the ``#doc-meta`` row the shared core builds the
control into.
"""
from __future__ import annotations
@@ -673,7 +697,7 @@ def test_viewer_url_builder_present_in_chat_and_sources() -> None:
assert 'chip.href = documentUrl(s.source, s.path, "/")' in app_js
assert 'chip.addEventListener("click"' in app_js
assert "e.preventDefault()" in app_js
assert "openDocumentModal(s.source, s.path, chip)" in app_js
assert "openDocumentModal(s.source, s.path, chip, \"/\")" in app_js
sources_js = _read(SOURCES_JS)
# Sources: unchanged 2-arg builder — no back param in the URL.
@@ -694,9 +718,8 @@ def _run_node(script: str) -> str:
def _extract_function(js: str, name: str) -> str:
match = re.search(
rf"(?:export )?function {name}\(source, path(?:, back = \"/\")?\) \{{.*?\n\}}", js, re.S
)
pat = rf"(?:export )?function {name}\(source, path(?:, back = (?:\"/\"|null))?\) \{{.*?\n\}}"
match = re.search(pat, js, re.S)
assert match, f"{name}(source, path) not found"
return match.group(0).replace("export ", "", 1)
@@ -878,14 +901,18 @@ def test_modal_close_contract_pins() -> None:
# The shared renderer (not a copy), called with the modal's own
# #doc-modal-title / #doc-modal-meta / #doc-modal-content elements.
assert "renderDocument(doc, { titleEl, metaEl, contentEl })" in js
assert 'openEl.href = fullPageUrl(source, path)' in js
assert 'openEl.href = fullPageUrl(source, path, currentBack)' in js
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_modal_url_builders_encode_like_the_page() -> None:
"""Behavioral check (node) of the modal's own URL builders: the
content fetch and the "Full page" href must come out percent-encoded
exactly like the page's builders (slashes/spaces in real paths)."""
exactly like the page's builders (slashes/spaces in real paths).
Phase 153 (task 01): the "Full page" builder's optional ``back``
(default null) — a null/absent ``back`` keeps the URL byte-identical
to pre-phase (no ``&back=`` at all — the Sources path), and a truthy
``back`` is appended + percent-encoded (``"/"`` → ``%2F``)."""
js = _read(MODAL_JS)
content_fn = _extract_function(js, "contentUrl")
full_fn = _extract_function(js, "fullPageUrl")
@@ -893,11 +920,18 @@ def test_modal_url_builders_encode_like_the_page() -> None:
content_fn
+ full_fn
+ "\nconsole.log(contentUrl('Homelab', 'notes/my file.yaml'));\n"
+ "console.log(fullPageUrl('H omelab', 'a/b.md'));"
+ "console.log(fullPageUrl('H omelab', 'a/b.md'));\n"
+ "console.log(fullPageUrl('s', 'p'));\n"
+ "console.log(fullPageUrl('s', 'p', '/'));\n"
+ "console.log(fullPageUrl('H omelab', 'a/b.md', '/sources.html'));"
)
assert out.splitlines() == [
"/api/documents/content?source=Homelab&path=notes%2Fmy%20file.yaml",
# pre-phase byte-identical: no ``back`` arg → no ``&back=``
"/document.html?source=H%20omelab&path=a%2Fb.md",
"/document.html?source=s&path=p",
"/document.html?source=s&path=p&back=%2F",
"/document.html?source=H%20omelab&path=a%2Fb.md&back=%2Fsources.html",
]
@@ -920,3 +954,752 @@ def test_viewer_js_rendering_contracts() -> None:
assert '"/sources.html"' in js # default target + no-JS fallback value
assert '"Chat"' in js and '"Sources"' in js # labels for the two entry points
assert "encodeURIComponent" in js # content fetch uses the same encoding
def _stmt_block(js: str, start_marker: str, last_stmt: str) -> str:
"""Extract the source of a statement group: from ``start_marker``
to the balanced close of the ``{…}`` opened by ``last_stmt``
(the brace walk is the house style — no third-party parsers)."""
start = js.find(start_marker)
assert start != -1, f"marker not found: {start_marker!r}"
stmt_i = js.find(last_stmt, start)
assert stmt_i != -1, f"statement not found: {last_stmt!r}"
brace_i = js.find("{", stmt_i)
depth = 0
for j in range(brace_i, len(js)):
if js[j] == "{":
depth += 1
elif js[j] == "}":
depth -= 1
if depth == 0:
return js[start : j + 1]
raise AssertionError(f"unbalanced braces from {last_stmt!r}")
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_phase13_back_resolution_guard() -> None:
"""The phase-13 back-resolution contract, guarded in BOTH layers
(task 03; the phase-13 pins in ``test_viewer_js_rendering_
contracts`` are untouched — this runs beside them):
1. SOURCE — the resolution block itself, extracted from the live
``document.js`` and pinned byte-for-byte (the exact statements,
including the two-label ternary);
2. BEHAVIOR — that exact extracted block (not a re-implementation)
run under node over a fake ``params``/``backLink``:
* missing / empty / garbage (absolute cross-origin) /
protocol-relative / pseudo-protocol ``back`` → the
``/sources.html`` default + the "Sources" label;
* ``/`` → the same-origin chat root WINS + the "Chat" label;
* any other same-origin relative → wins verbatim + the
"Back" label (and an explicit ``/sources.html`` keeps the
"Sources" label).
A refactor that re-introduces a browser-history heuristic, an
origin check, or a different label mapping fails the behavior run
(and a reformat of the block fails the byte pin)."""
js = _read(DOCUMENT_JS)
block = _stmt_block(
js,
'const backParam = params.get("back") || "";',
"if (backLabel) {",
)
# 1. the byte-for-byte source pin (the exact statements)
assert 'const backParam = params.get("back") || "";' in block
assert 'backParam.startsWith("/") && !backParam.startsWith("//")' in block
assert "? backParam" in block and ': "/sources.html";' in block
assert "backLink.href = backTarget;" in block
assert 'backTarget === "/"' in block # the label ternary…
assert '"Chat"' in block
assert 'backTarget === "/sources.html"' in block
assert '"Sources"' in block
assert '"Back"' in block # …and the three labels
# 2. the behavior — the EXACT extracted block, run under node
script = (
"const block = " + json.dumps(block) + ";\n"
"function run(v) {\n"
" const params = { get: (k) => (k === 'back' ? v : null) };\n"
" const label = { textContent: '' };\n"
" const backLink = { href: '',"
' querySelector: (s) => (s === "span" ? label : null) };\n'
' const f = new Function("params", "backLink",'
' block + "\\nreturn backTarget;");\n'
" const target = f(params, backLink);\n"
" return [target, label.textContent];\n"
"}\n"
"const cases = [null, '', 'https://evil.com/x', '//evil.com',"
" 'javascript:alert(1)', '/', '/sources.html', '/chat?x=1'];\n"
"for (const v of cases) console.log(JSON.stringify([v, ...run(v)]));\n"
)
out = [json.loads(line) for line in _run_node(script).splitlines()]
assert out == [
[None, "/sources.html", "Sources"], # missing
["", "/sources.html", "Sources"], # empty
["https://evil.com/x", "/sources.html", "Sources"], # garbage (cross-origin)
["//evil.com", "/sources.html", "Sources"], # protocol-relative
["javascript:alert(1)", "/sources.html", "Sources"], # pseudo-protocol
["/", "/", "Chat"], # the chat root wins
["/sources.html", "/sources.html", "Sources"], # explicit, same label
["/chat?x=1", "/chat?x=1", "Back"], # other same-origin relative wins
]
# ---------------------------------------------------------------------------
# Phase 153 (task 02) — "Copy path" (the standalone titlebar meta row +
# the modal meta block — one shared-core control, per-surface wiring)
# ---------------------------------------------------------------------------
DOCUMENT_MODAL_JS = _read(MODAL_JS) # the modal owner (the live asset)
COPY_PATH_STR = 'doc.source + "/" + doc.path' # the copied string (ASSUMPTION A1)
STATUS_TERNARY = (
'status.textContent = ok ? "Path copied." : "Path ready — copy it from the field."'
) # the status wording pair (owner-locked fallback wording, path-adapted)
def _fn_async(js: str, name: str) -> str:
"""Like _fn, but includes a leading ``async `` when the function is
async (the phase-153 helpers are async)."""
start = js.find(f"function {name}(")
assert start != -1, f"{name}() must exist"
if js[max(0, start - 6) : start] == "async ":
start -= 6
i = js.find("(", start)
depth = 0
close = i
while i < len(js):
if js[i] == "(":
depth += 1
elif js[i] == ")":
depth -= 1
if depth == 0:
close = i
break
i += 1
brace = js.find("{", close)
depth = 0
for j in range(brace, len(js)):
if js[j] == "{":
depth += 1
elif js[j] == "}":
depth -= 1
if depth == 0:
return js[start : j + 1]
raise AssertionError(f"unbalanced braces in {name}")
def test_copy_path_button_built_in_the_shared_core() -> None:
"""renderDocument (the ONE shared core — modal AND /document.html):
the Copy-path control joins the meta row AFTER the path code and
BEFORE the origin anchor (one build site — the surfaces can never
drift). The button is a labeled icon+text control: type=button,
class doc-copy-path, an aria-hidden SVG (createElementNS — the
never-build-HTML rule stands: the module's ONE innerHTML stays the
escape-first markdown render) and the VISIBLE "Copy path" text as
the accessible name (no redundant aria-label)."""
fn = _fn(_read(DOCUMENT_JS), "copyPathButton")
assert 'document.createElement("button")' in fn
assert "b.type = \"button\"" in fn
assert 'b.className = "doc-copy-path"' in fn
assert "document.createElementNS(SVG_NS, \"svg\")" in fn
assert 'icon.setAttribute("aria-hidden", "true")' in fn
assert 'label.textContent = "Copy path"' in fn
assert "innerHTML" not in re.sub(r"//.*?$|/\*.*?\*/", "", fn, flags=re.S | re.M)
rd = _fn(_read(DOCUMENT_JS), "renderDocument")
assert "copyPathButton()" in rd
# meta row order: path code → copy button → origin anchor (spread) →
# created badge (the phase-145 pin's slot, unchanged).
code_i = rd.find("pathCode,")
btn_i = rd.find("copyBtn,")
spread_i = rd.find("...originItems,")
created_i = rd.find("metaBadge(\"doc-created\"")
assert 0 <= code_i < btn_i < spread_i < created_i
def test_page_copy_path_helper_pins() -> None:
"""document.js's page-local helper (the per-page duplication house
style — the modal module keeps its own copy; no shared module):
* the copied string is `doc.source + "/" + doc.path` (the read-tool
form — the same two values the meta row renders);
* navigator.clipboard.writeText in a try (a non-secure http origin
rejecting the clipboard is the fallback trigger);
* the status ternary: "Path copied." (success, visual-only —
aria-hidden, the toast idiom) / "Path ready — copy it from the
field." (failure — the owner-locked fallback wording,
path-adapted);
* a re-click re-attempts (the previous .doc-copy-pop block is
removed first — a failed copy never clears the previous state
into nothing);
* the success status auto-clears ~4s (the block is dropped if it
is still connected);
* the page's #doc-copy-announcer (role=status) reads the outcome
(one a11y announcer — the visible node is aria-hidden);
* the fallback field: input-like (type text, readOnly, class
doc-path-fallback, prefilled with the path, selects its content
on focus, auto-focused with preventScroll — the
.share-link-fallback pattern);
* the page's state floats in a .doc-copy-pop block anchored under
the button (in the sticky header — the meta row never wraps or
grows the bar, the phase-12/34 contract)."""
js = _read(DOCUMENT_JS)
assert "async function copyPathWithFallback(btn, path)" in js
assert "function makePathFallbackField(path)" in js
assert 'document.querySelectorAll(".doc-copy-pop").forEach((el) => el.remove());' in js
assert "await navigator.clipboard.writeText(path);" in js
try_i = js.find("try {\n await navigator.clipboard.writeText(path);\n } catch")
assert try_i != -1, "the writeText call must sit in a try (the rejection fallback)"
assert js.count(STATUS_TERNARY) == 1
assert "status.setAttribute(\"aria-hidden\", \"true\")" in js # visual-only status
assert 'document.querySelector("#doc-copy-announcer")' in js
assert "if (block.isConnected) block.remove();" in js
assert "}, 4000);" in js # the auto-clear beat
assert js.count(COPY_PATH_STR) == 1 # the copied string, wired in render()
assert 'metaEl.querySelector(".doc-copy-path")' in js # the render() wiring
# the fallback field (makePathFallbackField)
f = _fn(js, "makePathFallbackField")
assert 'document.createElement("input")' in f
assert "field.type = \"text\"" in f
assert "field.readOnly = true" in f
assert 'field.className = "doc-path-fallback"' in f
assert "field.value = path" in f
assert 'field.setAttribute("aria-label", "Document path")' in f
assert "field.addEventListener(\"focus\", () => field.select())" in f
# the floating block (page surface)
p = _fn_async(js, "copyPathWithFallback")
assert 'block.className = "doc-copy-pop"' in p
assert 'document.querySelector("header.doc-header")' in p
assert "br.bottom - hr.top + 6" in p # anchored under the button
assert "field.focus({ preventScroll: true })" in p
def test_modal_copy_path_helper_pins() -> None:
"""document-modal.js's page-local helper (the per-page duplication
house style):
* the copied string is `doc.source + "/" + doc.path` (the read-tool
form — the same two values the meta row renders);
* writeText in a try; the status ternary (the same owner-locked
wording as the page);
* the nodes land right after the "Copy path" control
(insertAdjacentElement("afterend") — the meta block wraps, so
they may take a new row);
* a re-click re-attempts (the previous .doc-copy-status /
.doc-path-fallback state is removed first — a failed copy never
clears the previous state into nothing);
* success auto-clears ~4s; #doc-modal-desc (setDesc) reads the
outcome (one a11y announcer — the visible node is aria-hidden);
* the renderDocument call is followed by the wireCopyPath wiring
(the control is live on every open)."""
js = DOCUMENT_MODAL_JS
assert "async function copyPathWithFallback(metaEl, path)" in js
assert "function wireCopyPath(metaEl, doc)" in js
assert "let modalCopyTimer = 0" in js
assert "await navigator.clipboard.writeText(path);" in js
assert js.count(STATUS_TERNARY) == 1
assert 'status.setAttribute("aria-hidden", "true")' in js
assert js.count('".doc-copy-status, .doc-path-fallback"') == 1 # the re-attempt cleanup
assert js.count('insertAdjacentElement("afterend"') == 3 # 1 success + 2 failure
assert 'setDesc("Path copied.")' in js
assert 'setDesc("Path ready — copy it from the field.")' in js
assert "if (status.isConnected) status.remove();" in js
assert "}, 4000);" in js
assert js.count(COPY_PATH_STR) == 1 # wired in wireCopyPath
assert 'metaEl.querySelector(".doc-copy-path")' in js
# renderDocument → wireCopyPath, in openDocumentModal's fetch .then
# (the LAST occurrence — the docstring names the render earlier):
rd_i = js.rfind("renderDocument(doc, { titleEl, metaEl, contentEl });")
wire_i = js.rfind("wireCopyPath(metaEl, doc)")
assert rd_i != -1 and wire_i != -1
assert 0 < wire_i - rd_i < 300 # right after the render
# the shared core builds the button for BOTH surfaces — the modal
# module never builds one itself (no per-surface copy):
assert "copyPathButton" not in js
def test_document_html_carries_the_copy_announcer() -> None:
"""The /document.html page's role=status live region for the copy
outcome — visually hidden (the visible status line is the
aria-hidden floating node; this node is the sole a11y announcer,
the share pattern)."""
html = (FRONTEND / "document.html").read_text(encoding="utf-8")
i = html.find('id="doc-copy-announcer"')
assert i != -1
node = html[i - 200 : i + 200]
assert "visually-hidden" in node
assert "role=\"status\"" in node
assert "aria-live" in node
def test_document_html_carries_the_copy_path_static_nodes() -> None:
"""The ``document.html`` Copy-path static nodes (task 03 — the
Copy-path node on the full page): the floating ``.doc-copy-pop``
block is anchored in the page's ``header.doc-header`` (absent,
the page helper's ``if (header)`` guard would drop the block and
the status would never float — the meta row is never grown, the
phase-12/34 contract); the ``#doc-copy-announcer`` (role=status,
the sole a11y announcer) and the ``#doc-meta`` row (where the
shared core builds the control) must all be present for the
page surface to work."""
html = (FRONTEND / "document.html").read_text(encoding="utf-8")
assert '<header class="doc-header">' in html, (
"the page's sticky header is the copy block's anchor"
)
assert 'id="doc-copy-announcer"' in html, "the announcer node is missing"
assert 'role="status"' in html, "the announcer must be a live region"
assert 'id="doc-meta"' in html, "the meta row (the control's home) is missing"
def test_copy_path_css() -> None:
"""The Copy-path styles (phase 153, task 02):
* .doc-copy-path — the row-action language (inline-flex icon +
label), neutral secondary fill, brand-soft hover pair,
focus-visible outline; the ≤640px block gives it the ≥44px
touch-target floor (PLAN §7.1);
* .doc-copy-status — the visible status (ink-soft on surface —
the meta row's 5.1:1 pair), serving BOTH surfaces;
* .doc-copy-pop — the page's floating state block (position
absolute in the sticky header — never grows the bar), z-index
30 inside the header's z-20 root context;
* .doc-path-fallback — the .share-link-fallback pattern (input
variant): mono, ellipsis, surface fill, --line border."""
css = (FRONTEND / "assets" / "styles.css").read_text(encoding="utf-8")
bp = css.find(".doc-copy-path {")
assert bp != -1
b = css[bp : css.find("}", bp)]
assert "display: inline-flex" in b
assert "gap: 0.3rem" in b
assert "cursor: pointer" in b
assert ".doc-copy-path svg" in css
assert ".doc-copy-path:hover" in css
assert ".doc-copy-path:focus-visible" in css
assert ".doc-copy-path { min-height: 44px; min-width: 44px; }" in css # mobile floor
sp = css.find(".doc-copy-status {")
assert sp != -1
s = css[sp : css.find("}", sp)]
assert "color: var(--ink-soft)" in s
pp = css.find(".doc-copy-pop {")
assert pp != -1
p = css[pp : css.find("}", pp)]
assert "position: absolute" in p
assert "z-index: 30" in p
fp = css.find(".doc-path-fallback {")
assert fp != -1
f = css[fp : css.find("}", fp)]
assert "font-family: var(--mono)" in f
assert "text-overflow: ellipsis" in f
assert ".doc-path-fallback:hover" in css
assert ".doc-path-fallback:focus-visible" in css
def _copy_path_page_fixture() -> str:
"""The shared node fixture: a fake DOM (elements + query registry +
tracked pop blocks) + a fake clipboard (resolves or rejects) + a
fake timer (captures the fn + delay)."""
return """
function makeEl(tag) {
const el = {
tag, children: [], attrs: {}, style: {},
className: "", type: "", readOnly: false, value: "", title: "",
isConnected: true, focusCount: 0, selectCount: 0, listeners: {},
setAttribute(k, v) { this.attrs[k] = v; },
appendChild(c) { this.children.push(c); return c; },
append(...cs) { this.children.push(...cs); },
insertAdjacentElement(pos, node) {
if (pos === "afterend") this.after.unshift(node);
return node;
},
addEventListener(ev, fn) { (this.listeners[ev] = this.listeners[ev] || []).push(fn); },
focus() { this.focusCount += 1; (this.listeners.focus || []).forEach((f) => f()); },
select() { this.selectCount += 1; },
remove() { this.isConnected = false; },
getBoundingClientRect() {
return { top: 10, left: 20, bottom: 44, right: 90, width: 70, height: 34 };
},
};
el.after = [];
return el;
}
const created = [];
const registry = {};
let written = [];
let reject = false;
let timerFn = null;
let timerDelay = null;
globalThis.setTimeout = (fn, delay) => { timerFn = fn; timerDelay = delay; return 1; };
globalThis.clearTimeout = () => {};
// Node 24's globalThis.navigator is GETTER-ONLY (a plain assignment
// silently fails in sloppy mode) — defineProperty is the only way to
// stand up the fake clipboard.
Object.defineProperty(globalThis, "navigator", {
configurable: true,
value: {
clipboard: {
writeText: async (s) => { written.push(s); if (reject) throw new Error("denied"); },
},
},
});
globalThis.document = {
createElement: (t) => { const el = makeEl(t); created.push(el); return el; },
createElementNS: (ns, t) => makeEl(t),
querySelector: (sel) => {
if (!registry[sel]) registry[sel] = makeEl("root:" + sel);
return registry[sel];
},
querySelectorAll: (sel) =>
sel === ".doc-copy-pop"
? created.filter((c) => c.className === "doc-copy-pop" && c.isConnected)
: [],
};
let copyStatusTimer = 0;
"""
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_page_copy_path_behavior_under_node() -> None:
"""The page's copyPathWithFallback under node (a fake DOM + a fake
clipboard):
* success — the clipboard receives the EXACT `source + "/" + path`
string (spaces + slashes intact — the read-tool form,
ASSUMPTION A1), the visible status is "Path copied." (the
aria-hidden node), the announcer reads the same text, the block
is anchored under the button in the header (top/left computed
from the rects), and the auto-clear timer is scheduled at 4000
ms and drops the block when it fires;
* failure — no write, the pre-filled readOnly fallback field
(doc-path-fallback, aria-labelled "Document path") is
auto-focused and selects its content on focus, the status reads
"Path ready — copy it from the field." (the owner-locked
wording, path-adapted) and the announcer reads it too;
* a re-click re-attempts (failure again → exactly one connected
state block; then success → the field is replaced by the
success status — never cleared into nothing)."""
js = _copy_path_page_fixture()
js += _fn(_read(DOCUMENT_JS), "makePathFallbackField")
js += "\n" + _fn_async(_read(DOCUMENT_JS), "copyPathWithFallback")
js += """
async function main() {
let out = {};
const header = document.querySelector("header.doc-header");
const btn = document.createElement("button");
const ok1 = await copyPathWithFallback(btn, "H omelab/a/b c.md");
const pops = () => created.filter((c) => c.className === "doc-copy-pop");
const pop1 = pops()[pops().length - 1];
out = {
ok1,
written: written.slice(),
popClass: pop1.className,
inHeader: header.children.includes(pop1),
top: pop1.style.top,
left: pop1.style.left,
statusText: pop1.children.find((c) => c.className === "doc-copy-status").textContent,
statusAria: pop1.children.find((c) => c.className === "doc-copy-status").attrs["aria-hidden"],
announcer: registry["#doc-copy-announcer"].textContent,
timerDelay,
connected: pops().filter((p) => p.isConnected).length,
};
timerFn(); // the auto-clear fires
out.cleared = !pop1.isConnected;
reject = true;
const ok2 = await copyPathWithFallback(btn, "H omelab/a/b c.md");
const pop2 = pops().filter((p) => p.isConnected)[0];
const field = pop2.children.find((c) => c.tag === "input");
out.ok2 = ok2;
out.fieldValue = field.value;
out.fieldType = field.type;
out.fieldReadOnly = field.readOnly;
out.fieldClass = field.className;
out.fieldAria = field.attrs["aria-label"];
out.fieldTitle = field.title;
out.fieldFocus = field.focusCount;
out.fieldSelect = field.selectCount;
out.failStatus = pop2.children.find((c) => c.className === "doc-copy-status").textContent;
out.failAnnouncer = registry["#doc-copy-announcer"].textContent;
out.connectedAfterFail = pops().filter((p) => p.isConnected).length;
await copyPathWithFallback(btn, "H omelab/a/b c.md"); // re-attempt (fail again)
out.connectedAfterRetry = pops().filter((p) => p.isConnected).length;
reject = false;
const ok3 = await copyPathWithFallback(btn, "x/y.md"); // re-attempt (success)
out.ok3 = ok3;
out.lastWritten = written[written.length - 1];
const pop3 = pops().filter((p) => p.isConnected)[0];
out.successReplacesField = !pop3.children.some((c) => c.tag === "input");
console.log(JSON.stringify(out));
}
main().catch((e) => { console.error("FAIL " + e.message); process.exit(1); });
"""
r = _run_node(js)
out = json.loads(r)
assert out["ok1"] is True
assert out["written"] == ["H omelab/a/b c.md"] # the exact source/path form
assert out["popClass"] == "doc-copy-pop"
assert out["inHeader"] is True
assert out["top"] == "40px" and out["left"] == "0px" # anchored under the button
assert out["statusText"] == "Path copied."
assert out["statusAria"] == "true" # visual only
assert out["announcer"] == "Path copied." # the a11y region
assert out["timerDelay"] == 4000
assert out["connected"] == 1
assert out["cleared"] is True # the auto-clear dropped the block
assert out["ok2"] is False
assert out["fieldValue"] == "H omelab/a/b c.md" # prefilled with the path
assert out["fieldType"] == "text"
assert out["fieldReadOnly"] is True
assert out["fieldClass"] == "doc-path-fallback"
assert out["fieldAria"] == "Document path"
assert out["fieldTitle"] == "Document path — click, then copy (Ctrl/Cmd+C)"
assert out["fieldFocus"] == 1 # auto-focused (selects the path)
assert out["fieldSelect"] == 1 # the focus listener selected the content
assert out["failStatus"] == "Path ready — copy it from the field."
assert out["failAnnouncer"] == "Path ready — copy it from the field."
assert out["connectedAfterFail"] == 1
assert out["connectedAfterRetry"] == 1 # the re-click replaced, never cleared
assert out["ok3"] is True
assert out["lastWritten"] == "x/y.md"
assert out["successReplacesField"] is True # the field was swapped for the status
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_modal_copy_path_behavior_under_node() -> None:
"""The modal's copyPathWithFallback under node (a fake DOM + a
fake clipboard + the setDesc announcer):
* success — the clipboard receives the EXACT `source + "/" + path`
string, the "Path copied." status lands right after the control
(insertAdjacentElement("afterend")), the visible node is
aria-hidden, #doc-modal-desc reads the outcome, and the
auto-clear timer (4000 ms) drops the status when it fires;
* failure — the pre-filled readOnly fallback field + the
"Path ready — copy it from the field." status land in DOM order
[field, status] right after the control (the meta block wraps),
the field is auto-focused and selects its content on focus, and
#doc-modal-desc reads the outcome;
* a re-click re-attempts (the previous state is removed first —
exactly one connected state pair after the retry — a failed
copy never clears the previous state into nothing)."""
js = """
""" + _copy_path_page_fixture()
js += """
const desc = [];
function setDesc(t) { desc.push(t); }
let modalCopyTimer = 0;
"""
js += "\n" + _fn_async(DOCUMENT_MODAL_JS, "copyPathWithFallback")
js += """
async function main() {
let out = {};
const inserted = [];
const btn = makeEl("button");
btn.className = "doc-copy-path";
btn.insertAdjacentElement = function (pos, node) {
if (pos === "afterend") { this.after.unshift(node); inserted.push(node); }
return node;
};
const metaEl = {
appendChild(c) { inserted.push(c); return c; },
append(...cs) { inserted.push(...cs); },
querySelector(sel) { return sel === ".doc-copy-path" ? btn : null; },
querySelectorAll() { return inserted; },
};
const ok1 = await copyPathWithFallback(metaEl, "H omelab/a/b c.md");
const live = () =>
btn.after.filter(
(n) =>
(n.className === "doc-copy-status" || n.className === "doc-path-fallback") &&
n.isConnected,
);
out = {
ok1,
written: written.slice(),
after: live().map((n) => n.className),
statusText: live().find((n) => n.className === "doc-copy-status").textContent,
statusAria: live().find((n) => n.className === "doc-copy-status").attrs["aria-hidden"],
desc: desc.slice(),
timerDelay,
};
timerFn(); // the auto-clear fires
out.cleared = !btn.after[0].isConnected;
reject = true;
const ok2 = await copyPathWithFallback(metaEl, "H omelab/a/b c.md");
const lv = live();
const field = lv.find((n) => n.className === "doc-path-fallback");
out.ok2 = ok2;
out.afterFail = lv.map((n) => n.className);
out.fieldValue = field.value;
out.fieldType = field.type;
out.fieldReadOnly = field.readOnly;
out.fieldAria = field.attrs["aria-label"];
out.fieldFocus = field.focusCount;
out.fieldSelect = field.selectCount;
out.failStatus = lv.find((n) => n.className === "doc-copy-status").textContent;
out.descFail = desc[desc.length - 1];
out.connectedAfterFail = lv.length;
await copyPathWithFallback(metaEl, "H omelab/a/b c.md"); // re-attempt (fail again)
out.connectedAfterRetry = live().length;
console.log(JSON.stringify(out));
}
main().catch((e) => { console.error("FAIL " + e.message); process.exit(1); });
"""
r = _run_node(js)
out = json.loads(r)
assert out["ok1"] is True
assert out["written"] == ["H omelab/a/b c.md"] # the exact source/path form
assert out["after"] == ["doc-copy-status"] # right after the control
assert out["statusText"] == "Path copied."
assert out["statusAria"] == "true" # visual only
assert out["desc"] == ["Path copied."] # the a11y region (setDesc)
assert out["timerDelay"] == 4000
assert out["cleared"] is True # the auto-clear dropped the status
assert out["ok2"] is False
assert out["afterFail"] == ["doc-path-fallback", "doc-copy-status"] # field first, status after
assert out["fieldValue"] == "H omelab/a/b c.md" # prefilled with the path
assert out["fieldType"] == "text"
assert out["fieldReadOnly"] is True
assert out["fieldAria"] == "Document path"
assert out["fieldFocus"] == 1 # auto-focused (selects the path)
assert out["fieldSelect"] == 1 # the focus listener selected the content
assert out["failStatus"] == "Path ready — copy it from the field."
assert out["descFail"] == "Path ready — copy it from the field."
assert out["connectedAfterFail"] == 2 # one field + one status
assert out["connectedAfterRetry"] == 2 # the re-click replaced, never cleared
# ---------------------------------------------------------------------------
# Phase 153 (task 03) — the copy-path composition, end-to-end under node
# (the click handler + render() wiring → the EXACT composed bytes on the
# (fake) clipboard; the pre-existing pins above call the helper with a
# pre-composed string — these run the composition step itself)
# ---------------------------------------------------------------------------
PUMP = "async function pump() { for (let i = 0; i < 300; i++) await Promise.resolve(); }\n"
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_wire_copy_path_click_composes_the_exact_source_path_string() -> None:
"""The modal's ``wireCopyPath`` click handler, executed end-to-end
under node (the REAL ``wireCopyPath`` + the REAL ``
copyPathWithFallback`` from the live ``document-modal.js``, the
DOM/clipboard/timers faked): for ``source = "H omelab"`` (space) +
``path = "a/b c.md"`` (slashes + a space), a click sends the
EXACT ``"H omelab/a/b c.md"`` bytes to the clipboard (byte-for-
byte — a refactor that reorders, re-encodes, or trims the
composition breaks the exact string). The success beat announces
the exact success string; a REJECTED clipboard then prefills the
fallback field with that SAME composed string (the exact prefill,
readOnly, focused + selected — the "never clears the previous
state" re-click stands: the second status replaces the first)."""
modal_js = _read(MODAL_JS)
wire = _fn(modal_js, "wireCopyPath")
helper = _fn_async(modal_js, "copyPathWithFallback")
script = (
_copy_path_page_fixture()
+ "\nlet modalCopyTimer = 0;\n"
+ "const descCalls = [];\n"
+ "function setDesc(t) { descCalls.push(t); }\n"
+ "let createdInput = null;\n"
+ "document.createElement = (t) => { const el = makeEl(t);"
+ " if (t === 'input') createdInput = el; return el; };\n"
+ "const btn = makeEl('button');\n"
+ "const metaEl = { querySelector: (s) => (s === '.doc-copy-path' ? btn : null),"
+ " querySelectorAll: () => [], replaceChildren() {} };\n"
+ helper
+ "\n" + wire
+ "\n" + PUMP
+ "wireCopyPath(metaEl, { source: 'H omelab', path: 'a/b c.md' });\n"
+ "const newestStatus = () => (btn.after.find((e) => e.className === 'doc-copy-status')"
+ " || { textContent: undefined }).textContent;\n"
+ "const click = btn.listeners.click[0];\n"
+ "click(); await pump();\n"
+ "const okOut = { written: written.slice(), status: newestStatus(),"
+ " descCalls: descCalls.slice() };\n"
+ "reject = true; click(); await pump();\n"
+ "const failOut = { input: createdInput && createdInput.value,"
+ " readOnly: createdInput && createdInput.readOnly,"
+ " focusCount: createdInput && createdInput.listeners.focus.length,"
+ " selectCount: createdInput && createdInput.selectCount,"
+ " status: newestStatus() };\n"
+ "console.log(JSON.stringify({ okOut, failOut }));\n"
)
out = json.loads(_run_node(script))
# the exact composed bytes (spaces + slashes) on the clipboard
assert out["okOut"]["written"] == ["H omelab/a/b c.md"], (
"the click handler must send the exact source + '/' + path bytes"
)
assert out["okOut"]["status"] == "Path copied."
assert out["okOut"]["descCalls"] == ["Path copied."]
# the rejected-clipboard failure: the prefill is the SAME composed string
assert out["failOut"]["input"] == "H omelab/a/b c.md", (
"the fallback field must prefill with the exact composed string"
)
assert out["failOut"]["readOnly"] is True
assert out["failOut"]["focusCount"] == 1
assert out["failOut"]["selectCount"] == 1
assert out["failOut"]["status"] == "Path ready — copy it from the field."
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_page_render_wires_the_copy_path_with_the_composed_string() -> None:
"""The page's ``render()`` wiring, executed end-to-end under node
(the REAL ``render`` + the page-local ``copyPathWithFallback`` +
``makePathFallbackField`` from the live ``document.js``, the
DOM/clipboard/timers faked): a click on the shared core's
``.doc-copy-path`` control sends the EXACT ``"H omelab/a/b c.md"``
bytes to the clipboard (the ``doc.source + "/" + doc.path``
composition, byte-for-byte). The success beat floats the status in
the ``header.doc-header`` block (the meta row is never grown —
the phase-12/34 contract); the REJECTED-clipboard failure floats
the fallback field prefilled with that SAME composed string
(readOnly, focused + selected) beside the exact failure status, and
the role=status announcer mirror gets the same text."""
js = _read(DOCUMENT_JS)
render_fn = _fn(js, "render")
helper = _fn_async(js, "copyPathWithFallback")
field_fn = _fn(js, "makePathFallbackField")
script = (
_copy_path_page_fixture()
+ "const brand = () => 'Brain of Reese';\n"
+ "const rendered = [];\n"
+ "function renderDocument(d, els) { rendered.push(d); }\n"
+ "const btn = makeEl('button');\n"
+ "const titleEl = {};\n"
+ "const metaEl = { querySelector: (s) => (s === '.doc-copy-path' ? btn : null),"
+ " replaceChildren() {} };\n"
+ "const contentEl = {};\n"
+ "const header = document.querySelector('header.doc-header');\n"
+ field_fn
+ "\n" + helper
+ "\n" + render_fn
+ "\n" + PUMP
+ "render({ source: 'H omelab', path: 'a/b c.md', title: 'T' });\n"
+ "const click = btn.listeners.click[0];\n"
+ "click(); await pump();\n"
+ "const okBlock = header.children[0];\n"
+ "const okOut = { written: written.slice(), blockChildren: okBlock.children.length,"
+ " status: okBlock.children[0].textContent,"
+ " announcer: document.querySelector('#doc-copy-announcer').textContent };\n"
+ "reject = true; click(); await pump();\n"
+ "const failBlock = header.children[header.children.length - 1];\n"
+ "const field = failBlock.children[0]; const failStatus = failBlock.children[1];\n"
+ "const failOut = { value: field.value, readOnly: field.readOnly,"
+ " focusCount: field.listeners.focus.length, selectCount: field.selectCount,"
+ " status: failStatus.textContent, className: failBlock.className };\n"
+ "console.log(JSON.stringify({ okOut, failOut }));\n"
)
out = json.loads(_run_node(script))
# the exact composed bytes (spaces + slashes) on the clipboard
assert out["okOut"]["written"] == ["H omelab/a/b c.md"], (
"render() must wire the click to the exact source + '/' + path bytes"
)
# the float lives in the header (never the meta row): one status child
assert out["okOut"]["blockChildren"] == 1
assert out["okOut"]["status"] == "Path copied."
assert out["okOut"]["announcer"] == "Path copied." # the a11y mirror
# the rejected-clipboard failure: the prefill is the SAME composed string
assert out["failOut"]["value"] == "H omelab/a/b c.md", (
"the page fallback field must prefill with the exact composed string"
)
assert out["failOut"]["readOnly"] is True
assert out["failOut"]["focusCount"] == 1
assert out["failOut"]["selectCount"] == 1
assert out["failOut"]["status"] == "Path ready — copy it from the field."
assert out["failOut"]["className"] == "doc-copy-pop" # the floating block