phase: 166_hide_empty_answer_bubble
**Phase 166 — final verification pass: green.** All 3 task files already in `complete/`, so this was verification + defect/doc fixing.
- **Verified:** `.msg .bubble:empty { display: none; }` exists once in the Messages block with provenance; `frontend/` diff is styles.css only (**no app.js change**); `tests/unit/test_no_empty_answer_bubble.py` 26 pins pass; phase-158/161 anchor pins (thinking_timeline + tool_states + edit_message) **75 passed**.
- **Phase E2E in isolation:** `uv run pytest tests/e2e/test_no_empty_box.py --no-cov` → **5 passed**. Mutation check (rule removed) → 2 failures, so the suite genuinely pins the defect.
- **Regression set, isolated:** thinking_timeline 6 · turn_progress_loader 3 · chat_history 5 · share_chat 4 · save_share_ux 5 · failed_turn_retry 4 · chat_answer_clusters 4 · chat_error_honesty 4 → all pass.
- **Full gates:** `uv run pytest --cov=app --cov-report=term-missing` → **3880 passed, app/ 99%** (gate >90%); `uv run ruff check .` → clean; `uv run pyright` → **0 errors**.
- **Whole E2E tree swept file-by-file (150 files, own pytest process each).** 4 suites were broken by *earlier* phases; fixed and re-run green:
- `test_kb_tree`, `test_sync_summary_visibility`, `test_folder_summary_voice` — `td:nth-child(4) span` became a strict-mode violation when phase 156 wrapped the Edit label in `.cell-clip` → scoped to `span.kb-desc-text`.
- `test_search_tool_scope` — pinned the pre-`690fd98` per-run `<summary>`/`open` shape → re-pinned to the ONE `details.reasoning` container + `.tool-calls-summary`.
- `test_header_consistency` — `.doc-meta` pixel bound (`<34px`) was broken by phase 153's 44px touch-target floor at ≤640px → replaced with a shape-first single-line check that still fails a real wrap.
- **Completion criteria:** mid-turn box hidden ✅ · bubble appears at first delta, phase-130 rows below unchanged ✅ · restored/shared empty answer paints no box ✅ · no app.js change + anchors + full gates ✅ · commit/`complete/` move = harness's job (I ran no git index/history commands).
- **Docs:** README — added the phase-166 "no empty answer box mid-turn" note; fixed a **stale** Thinking lead ("collapsible Thinking blocks", pre-dated the 2026-10-06 one-disclosure refinement); QA section now states E2E isolation is mandatory (shared Postgres + fixed ports 8123/8901) with the per-file sweep loop. AGENTS.md — rule 5 UI checklist now names the phase-166 suppression rule.
- **Notable:** running all of `tests/e2e/` in one pytest session is not supported (fixture/port interleaving causes ~126 spurious failures) — documented in README. Restored the tracked `.agents/screenshots/doc_modal_scrollbar_1280.png` that `test_document_viewer.py` rewrites on every run.
- **Next pending phase:** `167_api_payload_diet`.
This commit is contained in:
@@ -92,6 +92,15 @@ question caps at 90 displayed characters with a `…` (phase 157) — the
|
||||
full text stays in the hover tooltip, and clicking the chip submits
|
||||
the full question.
|
||||
|
||||
**No empty answer box mid-turn** (phase 166): the bubble that will hold the
|
||||
answer is created on the turn's first `thinking`/`tool` frame — it is the
|
||||
DOM anchor the Reasoning container inserts itself above — but it stays
|
||||
hidden until the **first answer token** lands in it, so an in-flight turn
|
||||
shows the typing indicator and the Reasoning bubble only, never a padded
|
||||
empty rectangle (nothing replaces the box: the existing loading feedback
|
||||
is the turn's visible state). The same rule covers a restored or shared
|
||||
answer whose persisted text is empty, which paints no box either.
|
||||
|
||||
The document's meta row (the chat modal **and** the standalone viewer at
|
||||
`/document.html`) carries a **Copy path** button: one click copies the
|
||||
document's `source/path` (the exact form the `read` tool, the chips, and
|
||||
@@ -486,14 +495,15 @@ whole git-source refresh in one click:
|
||||
## Thinking
|
||||
|
||||
The self-hosted `turbo` model reasons before it answers. That reasoning is
|
||||
streamed with the turn as `thinking` SSE events and shown in **
|
||||
collapsible "Thinking" blocks** — one block per
|
||||
contiguous thinking run, in arrival order (phase 158) — all wrapped in
|
||||
**one collapsible "Reasoning" bubble** above the answer bubble
|
||||
(phase 161): the turn renders as a chronological timeline
|
||||
streamed with the turn as `thinking` SSE events and shown as **"Thinking"
|
||||
blocks** — one block per contiguous thinking run, in arrival order
|
||||
(phase 158) — all wrapped in **one collapsible "Reasoning" bubble** above
|
||||
the answer bubble (phase 161): the turn renders as a chronological timeline
|
||||
(Thinking → Tool call (1) → Thinking → … → answer), condensed into a
|
||||
single bounded live window (320 px, one scroll surface) that follows
|
||||
the tail while you watch.
|
||||
the tail while you watch. The Reasoning bubble is the ONE disclosure
|
||||
(owner refinement, 2026-10-06): the per-run blocks inside it are plain and
|
||||
never fold on their own.
|
||||
|
||||
The bubble is created on the turn's first thinking (or tool) frame and
|
||||
stays **open** from creation until the turn settles — including a
|
||||
@@ -1174,11 +1184,21 @@ uv run pyright
|
||||
# Playwright E2E — install the browser once:
|
||||
uv run playwright install chromium
|
||||
|
||||
# Each phase's E2E runs IN ISOLATION:
|
||||
# Each phase's E2E runs IN ISOLATION — one file per run:
|
||||
uv run pytest tests/e2e/test_import_documents.py -v --no-cov
|
||||
uv run pytest tests/e2e/test_chat_rag.py -v --no-cov
|
||||
# ...one file per phase in tests/e2e/ (the stories live in
|
||||
# .agents/user_stories/, see .agents/phases/todo/)
|
||||
# .agents/user_stories/, see .agents/phases/todo/).
|
||||
#
|
||||
# Isolation is REQUIRED, not a style preference: every suite shares one
|
||||
# Postgres and one pair of fixed ports (the app on 8123, the mock aipi on
|
||||
# 8901 — overridable with E2E_APP_PORT / E2E_MOCK_PORT), and each file
|
||||
# truncates + reseeds the KB for its own fixture. Running the whole
|
||||
# `tests/e2e/` directory in a single pytest session interleaves those
|
||||
# fixtures and fails tests that pass file by file.
|
||||
|
||||
# To sweep every suite, loop over the files (one pytest process each):
|
||||
for f in tests/e2e/test_*.py; do uv run pytest "$f" --no-cov -q || echo "FAIL $f"; done
|
||||
```
|
||||
|
||||
**Retrieval regression battery.** `tests/fixtures/retrieval_battery.txt` is
|
||||
|
||||
Reference in New Issue
Block a user