phase: 182_verbatim_show_text
**Phase 182 — final verification pass (all 8 tasks already complete; this was verification + docs + one defect fix).**
- Verified: `app/rag/verbatim.py` (locator grammar, `splitlines()` slice, line-boundary cap, `resolve_answer_blocks`), `SHOW_TEXT_TOOL` + `shown_texts`, `SHOW_TEXT_CLAUSE`, `done.answer_text` (9 fields, key dropped when empty) + `ChatMessageRecord.answer_text` (≤50, 600 000-char cap counted in the 8 M budget), `verbatim.js` + 4 `{verbatim:true}` renderers, `Containerfile` minify step, `.env.example` 3 vars.
- `uv run python -c "…answer_text, show_text_max_chars, show_text_max_blocks"` → `True 12000 5`; each negative refuses startup naming its field; `0` accepted as inert ✅
- `bash .agents/validate.sh` → **validation OK**: 4320 unit+integration tests green, `TOTAL 6849 17 → 99%` (gate app/ >90% OK), `ruff check .` "All checks passed!", `pyright` 0 errors (re-run after my edits, 08:28).
- E2E, each in isolation `--no-cov`: `test_verbatim_show_text.py` 13 passed (panel bytes == `documents.content`, server caption, clipboard == server string, `/document.html?source=…&path=…`, honest unresolved state, reload+share, `show_image` regression, flag-off app) ✅; green: `test_answer_images` `test_harness_aligned_tools` `test_agent_document_tools` `test_markdown_tables` `test_markdown_inline_links_images` `test_svg_document_preview` `test_smoke` `test_document_viewer` `test_markdown_blockquotes` `test_chat_persistence` `test_thinking_timeline` `test_chat_rag` `test_settings_mobile_value_width`.
- **Defect found & fixed (pre-existing, not phase 182):** `tests/e2e/test_admin_settings_panel.py::test_admin_sees_groups_and_source_badges` failed — its whole-page `assert "reese" not in page.content()` tripped on the shell's onboarding chips, which follow the newest saved chats in the one shared E2E DB (a leftover `reeselink.json` question from a tool suite). Scoped the strict sweep to `.settings-shell` and kept a whole-page `reese:reese` credential needle → 12 passed with the leftovers still present.
- Docs: README fixed 2 inconsistencies — Quick Start omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the app fail-loud refuses boot) and the broken anchor `#chat--` → `#chat-`; full re-read of README (1816 lines) + AGENTS confirmed the phase-182 feature/settings/log/UI-contract text matches the code, and every `.env.example` var + every `Settings` field is documented.
- Notable: kept `resolve_answer_blocks` synchronous on the SSE terminal path (bounded indexed SELECTs, matching the existing `query_log` precedent and the phase's documented fence-lookup bound); no live-infra changes made.
- Next pending phase: none — `todo/` holds only 182 (its 8 task files are in `complete/`; the harness commits + moves `00_phase.md`).
This commit is contained in:
@@ -63,6 +63,11 @@ from pathlib import Path
|
||||
|
||||
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
|
||||
APP_JS = FRONTEND / "assets" / "app.js"
|
||||
# Phase 182 (task 07): the ONE clipboard implementation moved to the shared
|
||||
# verbatim panel client; the share path delegates to it, so the fallback's
|
||||
# internals are pinned there (the owner-locked behavior is re-pinned, not
|
||||
# dropped).
|
||||
VERBATIM_JS = FRONTEND / "assets" / "verbatim.js"
|
||||
# Phase 76 (task 02): SOURCES_HTML + GIT_SOURCES_HTML dropped — both
|
||||
# views are folded into the shell (index.html); both files deleted.
|
||||
INDEX_HTML = FRONTEND / "index.html"
|
||||
@@ -533,18 +538,36 @@ def test_share_current_chat_save_then_share_branch() -> None:
|
||||
assert finally_idx != -1 and "shareBtn.disabled = false" in body[finally_idx:], (
|
||||
"the button is re-enabled in the finally — never stale"
|
||||
)
|
||||
# The clipboard + fallback helpers live in app.js (the chat page's
|
||||
# copy of the per-page helper).
|
||||
# Phase 182 (task 07): the clipboard + fallback pair MOVED to
|
||||
# assets/verbatim.js so the answer's verbatim panels and ordinary code
|
||||
# blocks share ONE degradation with the share link. The owner-locked
|
||||
# behavior is unchanged and re-pinned on its new home: the same host (the
|
||||
# composer, beside the status line — it rides the delegation), the same
|
||||
# transient `.share-link-fallback` field, the same select-on-focus, the
|
||||
# same XSS contract (textContent only), the same title string.
|
||||
copy = _fn(js, "copyShareLinkWithFallback")
|
||||
assert "navigator.clipboard.writeText(absoluteUrl)" in copy
|
||||
assert 'field.className = "share-link-fallback"' in copy
|
||||
assert "field.href = absoluteUrl" in copy
|
||||
assert "field.textContent = absoluteUrl" in copy, "XSS contract: textContent only"
|
||||
assert 'field.addEventListener("focus", () => selectAllInField(field))' in copy, (
|
||||
"select-on-focus — the field-like behavior"
|
||||
assert "copyTextWithFallback(" in copy, (
|
||||
"the share path delegates — ONE clipboard implementation"
|
||||
)
|
||||
assert "composer.appendChild(field)" in copy, "near the status line (the composer)"
|
||||
sel = _fn(js, "selectAllInField")
|
||||
assert "absoluteUrl," in copy, "the shared URL is what gets copied"
|
||||
assert "composer," in copy, "near the status line (the composer)"
|
||||
assert '"Share link — click, then copy (Ctrl/Cmd+C)"' in copy, (
|
||||
"the owner-locked field title rides the delegation byte-identically"
|
||||
)
|
||||
ver = VERBATIM_JS.read_text(encoding="utf-8")
|
||||
impl = _fn(ver, "copyTextWithFallback")
|
||||
assert "await navigator.clipboard.writeText(text)" in impl
|
||||
assert 'field.className = "share-link-fallback"' in impl
|
||||
assert "field.href = text" in impl, "a URL field carries the href"
|
||||
assert "field.textContent = text" in impl, "XSS contract: textContent only"
|
||||
assert (
|
||||
'field.addEventListener("focus", () => selectAllInFallbackField(field))' in impl
|
||||
), "select-on-focus — the field-like behavior"
|
||||
assert "host.appendChild(field)" in impl, "appended to the caller's host"
|
||||
assert (
|
||||
'document.querySelectorAll(".share-link-fallback").forEach((el) => el.remove())' in impl
|
||||
), "one field at a time (a new offer replaces the old)"
|
||||
sel = _fn(ver, "selectAllInFallbackField")
|
||||
assert "document.createRange()" in sel and "selectNodeContents(el)" in sel
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user