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

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

- Verified: `app/rag/verbatim.py` (locator grammar, `splitlines()` slice, line-boundary cap, `resolve_answer_blocks`), `SHOW_TEXT_TOOL` + `shown_texts`, `SHOW_TEXT_CLAUSE`, `done.answer_text` (9 fields, key dropped when empty) + `ChatMessageRecord.answer_text` (≤50, 600 000-char cap counted in the 8 M budget), `verbatim.js` + 4 `{verbatim:true}` renderers, `Containerfile` minify step, `.env.example` 3 vars.
- `uv run python -c "…answer_text, show_text_max_chars, show_text_max_blocks"` → `True 12000 5`; each negative refuses startup naming its field; `0` accepted as inert ✅
- `bash .agents/validate.sh` → **validation OK**: 4320 unit+integration tests green, `TOTAL 6849 17 → 99%` (gate app/ >90% OK), `ruff check .` "All checks passed!", `pyright` 0 errors (re-run after my edits, 08:28).
- E2E, each in isolation `--no-cov`: `test_verbatim_show_text.py` 13 passed (panel bytes == `documents.content`, server caption, clipboard == server string, `/document.html?source=…&path=…`, honest unresolved state, reload+share, `show_image` regression, flag-off app) ✅; green: `test_answer_images` `test_harness_aligned_tools` `test_agent_document_tools` `test_markdown_tables` `test_markdown_inline_links_images` `test_svg_document_preview` `test_smoke` `test_document_viewer` `test_markdown_blockquotes` `test_chat_persistence` `test_thinking_timeline` `test_chat_rag` `test_settings_mobile_value_width`.
- **Defect found & fixed (pre-existing, not phase 182):** `tests/e2e/test_admin_settings_panel.py::test_admin_sees_groups_and_source_badges` failed — its whole-page `assert "reese" not in page.content()` tripped on the shell's onboarding chips, which follow the newest saved chats in the one shared E2E DB (a leftover `reeselink.json` question from a tool suite). Scoped the strict sweep to `.settings-shell` and kept a whole-page `reese:reese` credential needle → 12 passed with the leftovers still present.
- Docs: README fixed 2 inconsistencies — Quick Start omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the app fail-loud refuses boot) and the broken anchor `#chat--` → `#chat-`; full re-read of README (1816 lines) + AGENTS confirmed the phase-182 feature/settings/log/UI-contract text matches the code, and every `.env.example` var + every `Settings` field is documented.
- Notable: kept `resolve_answer_blocks` synchronous on the SSE terminal path (bounded indexed SELECTs, matching the existing `query_log` precedent and the phase's documented fence-lookup bound); no live-infra changes made.
- Next pending phase: none — `todo/` holds only 182 (its 8 task files are in `complete/`; the harness commits + moves `00_phase.md`).
This commit is contained in:
2026-10-15 08:33:12 -04:00
parent 814a400e6d
commit b34b8ca672
104 changed files with 10852 additions and 203 deletions
+72 -6
View File
@@ -982,6 +982,10 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
turnAbort?.abort(); // phase 48: same outcome, one owner — aborted is
// set first, so the catch never reads the guard's
// abort as a user stop
// Phase 182 (task 07): the 300 s guard is a terminal frame — a panel
// still reading "Retrieving the exact text…" would be a lie by then
// (the hydrate at `done` never runs), so settle it to the honest state.
settleVerbatimPlaceholders(wrap?.querySelector(".bubble"), "unverified");
setUiState(UI_STATE.error, "That's taking a long time — the answer may be stuck.");
});
@@ -1283,7 +1287,14 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// NEW run in the container — the D15 re-open of the shared
// block is gone). Idempotent — a no-op without an active run.
closeCurrentRun();
wrap.querySelector(".bubble").innerHTML = renderMarkdown(acc);
// Phase 182 (task 07): the OPT-IN verbatim pass on the ONE answer
// renderer. A ` ```bor ` locator fence becomes a pending placeholder
// here (the model's re-typed body DROPPED, locked A7) that the `done`
// branch below hydrates against the frame's server-resolved blocks —
// mid-stream the reader sees "Retrieving the exact text…", never a
// copy that might be wrong. Every other renderer on the page (user
// bubbles, thinking runs, the document viewer) stays one-argument.
wrap.querySelector(".bubble").innerHTML = renderMarkdown(acc, { verbatim: true });
// No page scroll (phase 42): the answer never follows the viewport.
} else if (ev.type === "done") {
sawDone = true;
@@ -1315,6 +1326,25 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// figure is additive — "summaries should still be provided");
// a frame without it renders byte-identically (no figure DOM).
if (ev.answer_image) appendAnswerImage(wrap, ev.answer_image);
// Phase 182 (task 07, locked A1/A2): the answer's verbatim blocks —
// `ev.answer_text`, the bytes the SERVER read out of the index — land
// in the SAME band as the answer image (below the text, above the
// source clusters): hydrate every placeholder the renderer painted,
// then append the blocks the answer never located (a `show_text` call
// the prose never named). A locator the server could not resolve
// renders the honest unresolved sentence with NONE of the model's
// bytes, and no panel is left at "Retrieving the exact text…".
hydrateVerbatim(
wrap.querySelector(".bubble"),
ev.answer_text || [],
VERBATIM_LINK_OPTS
);
appendUnlocatedVerbatim(wrap, ev.answer_text || [], VERBATIM_LINK_OPTS);
// The turn SETTLED — this is where the ordinary code blocks get their
// Copy buttons (never mid-stream: per-delta the bubble is rebuilt from
// scratch, and a button that vanishes on the next token is not a
// control). NO badge, NO caption on those (locked A5).
enhanceAnswerCodeBlocks(wrap);
appendSources(wrap, ev.sources);
// Phase 113 (task 02): the cited tier stays the citation
// surface (the chips above); the related tier (scored docs
@@ -1332,7 +1362,11 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// what gets persisted.
const finalText = acc || (sawThinking ? EMPTY_ANSWER_FALLBACK : "");
if (!acc && sawThinking) {
wrap.querySelector(".bubble").innerHTML = renderMarkdown(finalText);
// Phase 182 (task 07): the same opt-in as the per-delta write —
// this line paints the answer the user keeps (the fallback text,
// so no placeholder exists in practice, but the ONE answer
// renderer must stay ONE code path in shape).
wrap.querySelector(".bubble").innerHTML = renderMarkdown(finalText, { verbatim: true });
}
// Persistence save point 2: the answer lands only when the turn is
// complete (raw text + the done metadata; phase 17: + optional
@@ -1359,6 +1393,13 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// the precedent): an image-less / pre-phase turn stores a
// byte-identical record.
answer_image: ev.answer_image || undefined,
// Phase 182 (task 07): the verbatim blocks persist with the turn
// (task 05's additive `answer_text` key) so a reload — and a shared
// page — re-renders the identical panel. `undefined` drops the key
// from the JSON (the house optional-meta pattern, the answer_image
// line above is the precedent): a pre-phase or answer_text=False
// record stays byte-identical.
answer_text: ev.answer_text || undefined,
// Phase 113 (task 02): the related tier persists with the
// turn (undefined drops the key from the JSON — the house
// optional-meta pattern), so the restore path re-renders the
@@ -1431,6 +1472,14 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
}
}
} else if (ev.type === "error") {
// Phase 182 (task 07): an `error` frame is terminal — the `done`
// hydrate will never run, so a placeholder that streamed in stays at
// "Retrieving the exact text…" forever unless it settles HERE (the
// honest unresolved state, never a stale promise of the exact text).
settleVerbatimPlaceholders(
wrap?.querySelector(".bubble"),
"unverified"
);
// Phase 114 (TODO L6): carry the frame's optional hint (the
// "question too long" frame has one) through the throw — the
// banner shows it in place of the default reachability hint.
@@ -1485,6 +1534,10 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
}
} catch (err) {
if (aborted) {
// Phase 182 (task 07): defensive settle for the guard's own abort
// surface (the timeout callback already ran it — idempotent, and the
// turn must not leave a pending panel on ANY terminal path).
settleVerbatimPlaceholders(wrap?.querySelector(".bubble"), "unverified");
// The 300s guard already took the turn to the error state — its
// own abort surfaces here, and it is never read as a user stop.
// Phase 159 (task 03): the aborted settle must not strand the
@@ -1505,6 +1558,10 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// convention; no sources/suggestions — the turn never settled).
// The persistedOnLeave guard (phase 20) keeps a navigate-away —
// which aborts the same fetch — from saving the partial twice.
// Phase 182 (task 07): the STOP is terminal for this answer too — a
// placeholder in the kept partial settles to the honest state (the
// blocks never arrive without a `done` frame).
settleVerbatimPlaceholders(wrap?.querySelector(".bubble"), "unverified");
if (wrap && acc && !persistedOnLeave) {
// Phase 158 (D5): the turn was stopped — fold every open run
// (replaces the phase-117 closeToolCalls fold + the D15 block
@@ -1635,14 +1692,23 @@ def test_run_turn_is_pinned_at_phase_159_state() -> None:
list-of-objects no longer stringifies to ``[object Object]``; the
task-03 pre-stream throw carries ``bubbleText`` (FAILED_REQUEST_TEXT)
+ the phase-114 ``hint``, and the catch forwards ``bubbleText:
err?.bubbleText`` into the failed-turn funnel — locked A3): the
full source must match the pin — the ``reask`` path, the turn-local
err?.bubbleText`` into the failed-turn funnel — locked A3)
and phase 182 (task 07)'s sanctioned additions (the two answer
renderers pass the verbatim opt-in — the per-delta write and the
``done``-time re-render; the done settle hydrates the `` ```bor ``
placeholders against ``ev.answer_text``, appends the blocks the answer
never located, and gives the settled answer's ordinary code blocks their
Copy buttons BEFORE ``appendSources``; the ``answer_text`` meta key rides
``rememberBrainTurn`` with ``|| undefined``; and the terminal paths — the
``error`` frame, the 300 s guard, its own abort surface, and the Stop
branch — settle any leftover placeholder to the unresolved state):
the full source must match the pin — the ``reask`` path, the turn-local
resets, the
SSE contract, the save points, and the sanctioned additions (the
task-01 ``lastUserWrap`` assignment + the idle-settle mark
pass, the task-03 aborted-settle mark pair, the phase-160
done-settle pair, the phase-161 live-container additions, and
the D5 refinement) included."""
done-settle pair, the phase-161 live-container additions, the
D5 refinement, and the phase-182 verbatim settle/hydrate pair) included."""
assert _fn_source(_js(), "runTurn") == PINNED_RUN_TURN