phase: 123_chat_image_questions
Build and Push Containers / build-and-push-app (push) Successful in 1m54s
Build and Push Containers / build-and-push-db (push) Failing after 1m50s

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:
2026-09-25 05:19:18 -04:00
parent a19d78d284
commit bef24e05e2
48 changed files with 3910 additions and 71 deletions
+22 -7
View File
@@ -313,9 +313,11 @@ def test_chat_bottom_unit_is_last_child_of_the_chat_shell() -> None:
`.chat-shell` is the `.chat-bottom` wrapper — NO id (nothing in JS
binds it; the bindings live on the inner elements, the move is pure
HTML/CSS) — holding the `.chat-actions` row, the phase-104
`#char-count` counter, and the `#composer` form, in that order: the
row + counter + composer are ONE sticky unit, and the wrapper owns
the shell's bottom slot, so the sticky shift range is still that
`#char-count` counter, the phase-123 `#attach-preview` strip (hidden
by default — zero height at rest, the sticky geometry untouched),
and the `#composer` form, in that order: the row + counter +
preview + composer are ONE sticky unit, and the wrapper owns the
shell's bottom slot, so the sticky shift range is still that
column's box (a sibling after it would carve the range away and
re-break the pin). The composer form keeps `novalidate` and its
contract ids."""
@@ -331,11 +333,12 @@ def test_chat_bottom_unit_is_last_child_of_the_chat_shell() -> None:
"elements"
)
kids = last["children"]
assert len(kids) == 3, (
"the unit holds exactly three element children: .chat-actions, "
"then #char-count (phase 104), then #composer"
assert len(kids) == 4, (
"the unit holds exactly four element children: .chat-actions, "
"then #char-count (phase 104), then #attach-preview (phase 123), "
"then #composer"
)
row, counter, form = kids
row, counter, preview, form = kids
assert row["tag"] == "div" and (
row["attrs"].get("class") or ""
).split() == ["chat-actions"], (
@@ -354,6 +357,18 @@ def test_chat_bottom_unit_is_last_child_of_the_chat_shell() -> None:
"the counter ships hidden — it appears only from 80% of the "
"4,000-char cap (app.js updateCharCount)"
)
# Phase 123 (task 02, TODO L6): the attach preview strip — a
# hidden-by-default div between the counter and the composer (the
# selected image above the input row; zero height while hidden, the
# pinned-cluster geometry untouched).
assert preview["tag"] == "div" and (
preview["attrs"].get("id") == "attach-preview"
), "the third child is the phase-123 #attach-preview strip"
assert (preview["attrs"].get("class") or "") == "attach-preview"
assert "hidden" in preview["attrs"], (
"the strip ships hidden — it appears only while a file is "
"attached (app.js attachedImage)"
)
assert form["tag"] == "form" and form["attrs"].get("id") == "composer"
assert "novalidate" in form["attrs"], (
"phase 48: the composer form stays `novalidate` (a `required` "