phase: 123_chat_image_questions
All gates green. Verification complete. **Phase 123 — final verification pass (all 4 tasks already in `complete/`)** - Verified the full implementation is in the working tree: `app/api/chat_images.py` (upload/serve pair), `ChatRequest.image`/`ChatMessage.image` (path-validated, omitted-when-None), toggle-off + stale-file hinted error frames, `build_user_content` multimodal build at both sites (chat.py deflected branch + `run_agent`), config-gated composer attach/preview/upload-then-send, restore + shared rendering, CSP `img-src 'self' data:` carve-out, mock-LLM capture buffer. - `uv run pytest` → **2796 passed**, exit 0 (unit + integration). - `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (29/4615 missed; phase-123 modules 99–100%). - `uv run pytest tests/e2e/test_chat_image_questions.py -v --no-cov` → **5 passed** in isolation. - `uv run ruff check . && uv run pyright` → clean (0 errors). **Completion criteria:** (1) attach→send→multimodal text+image to the model, bubble/reload/shared all render it, saved chat stores the PATH with `"base64" not in json.dumps(stored)` — **verified** (E2E tests 1–4 + integration round-trip); (2) `BOR_IMAGES=false` — control hidden, exact hinted error frame, zero model calls / no query_log row — **verified** (E2E test 5 + integration); (3) text-only byte-identical (`content` stays a plain `str`) — **verified** (unit + integration); (4) all gates green — **verified**; (5) commit + phase move — left to the harness per pipeline rules (no `git add`/`commit` run). No defects found; no live-infrastructure changes (repo + local dev DB only). **Next pending phase: none** — 123 is the last phase in `todo/`.
This commit is contained in:
@@ -93,12 +93,16 @@ def test_scroll_helper_is_unconditional() -> None:
|
||||
|
||||
|
||||
def test_add_message_takes_explicit_scroll_intent() -> None:
|
||||
"""addMessage(who, html, scroll = false): the phase-18
|
||||
"""addMessage(who, html, scroll = false, image = null): the phase-18
|
||||
scrollBehavior/force parameters are gone; the bubble scrolls only
|
||||
when the caller explicitly asks (submit reveal, restore landing)."""
|
||||
when the caller explicitly asks (submit reveal, restore landing).
|
||||
Phase 123 (task 02) appended the optional `image` argument (the
|
||||
question's attached image — { src, alt } on a user bubble, the ONE
|
||||
renderer for live + restore + shared); the scroll contract is
|
||||
untouched."""
|
||||
js = _js()
|
||||
body = _fn_body(js, "addMessage")
|
||||
assert "function addMessage(who, html, scroll = false)" in body
|
||||
assert "function addMessage(who, html, scroll = false, image = null)" in body
|
||||
assert "if (scroll) scrollReveal(wrap)" in body
|
||||
assert "force" not in body
|
||||
assert "scrollBehavior" not in body
|
||||
@@ -119,9 +123,13 @@ def test_submit_reveals_user_message() -> None:
|
||||
assert turn != -1, "runTurn must exist (phase 49 extraction)"
|
||||
body = js[turn : js.find("\n}\n", turn)]
|
||||
assert "if (!reask) {" in body, "the user append is gated on !reask"
|
||||
assert 'addMessage("user", renderMarkdown(text), true)' in body, (
|
||||
"the submit must reveal the user message (scroll intent true)"
|
||||
)
|
||||
# Phase 123 (task 02): the user append gained the optional image
|
||||
# argument (the attached image's { src, alt }) — the call is
|
||||
# multi-line now; the contract is the same: user + the raw text +
|
||||
# the explicit scroll intent true.
|
||||
assert re.search(
|
||||
r'addMessage\(\s*"user",\s*renderMarkdown\(text\),\s*true', body
|
||||
), "the submit must reveal the user message (scroll intent true)"
|
||||
for call in re.findall(r'addMessage\("brain"([^)]*)\)', body):
|
||||
assert "true" not in call, (
|
||||
f"streaming brain bubbles must not scroll the page: {call!r}"
|
||||
@@ -168,7 +176,16 @@ def test_restore_landing_is_one_shot() -> None:
|
||||
assert 'addMessage("brain", renderMarkdown(m.text), true)' in body
|
||||
assert js.count('"auto", true') == 0, "the old forced 'auto' landing must be gone"
|
||||
# Submit reveal + the two restore landings — nothing else scrolls.
|
||||
assert js.count(", true)") == 3, "only submit + the two restore calls may scroll"
|
||||
# Phase 123 (task 02): the submit call is multi-line (the optional
|
||||
# image argument follows the scroll intent), so it no longer ends
|
||||
# in the single-line ", true)" literal — the two restore calls do;
|
||||
# the submit reveal is counted by its own (multi-line) shape.
|
||||
assert js.count(", true)") == 2, (
|
||||
"only the two restore calls may scroll (single-line shape)"
|
||||
)
|
||||
assert len(re.findall(r'addMessage\(\s*"user",\s*renderMarkdown\(text\),\s*true', js)) == 1, (
|
||||
"the submit reveal may scroll (multi-line since phase 123's image argument)"
|
||||
)
|
||||
# The marker comment documents the one-shot, load-time contract.
|
||||
assert "restore landing" in body
|
||||
assert "one-shot" in body
|
||||
|
||||
Reference in New Issue
Block a user