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:
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user