phase: 150_drop_sync_result
Build and Push Containers / build-and-push-app (push) Successful in 2m24s
Build and Push Containers / build-and-push-db (push) Successful in 13s

**Phase 150 final verification pass — complete, 100% green.** All 6 task files were already in `complete/`, so this was verification + doc pass.

- Verified: `#sync-progress` is the sole sync text surface + sole `aria-live` region (`role="status"`, polite, no `aria-hidden`); `span#sync-result` gone from HTML/JS/CSS/tests (incl. runtime-built `RETIRED_ID` absence pins in the unit suites); `setSyncProgress` single writer with text-driven visibility; running-only box write in `setSyncLabel`; all 9 settle/cancel sites routed; `requestCancel` pre-fetch clear is a NO-OP.
- Tests: `uv run pytest` → **3463 passed**; `uv run pytest --cov=app` → **99% on app/** (>90%); `uv run ruff check .` + `uv run pyright` → clean (0 errors); `tests/e2e/test_sync_progress_box.py --no-cov` → **8 passed in isolation**; all 18 migrated sync/image-PDF E2E files green in isolation (77 tests).
- Completion criteria: 1) grep clean ✅ 2) sole live region, attrs correct ✅ 3) in-flight/settle/cancel/idle box states proven by E2E + unit pins ✅ 4) icon-only running button + restore ✅ 5) full gates green ✅ 6) no completed-phase behavior change (18 family suites + full suite green) ✅ 7) commit — left to harness per pass instructions (working tree untouched, no `git add`/`commit`).
- Documentation: README.md fixed 2 pre-existing inconsistencies (stale `~/Homelab`+`~/Deployments` source model in the intro — retired in phase 61 — now describes the git/local/archive Sources model; "Requires AIPI_KEY" in retrieval-quality now states `BOR_LLM_API_KEY` in `.env` or `$AIPI_KEY`, matching the scripts' real convention). Everything else (URL table, env defaults vs `app/config.py`, sync/phase-150 copy, prod claims) verified accurate. AGENTS.md: verified consistent — no changes needed.
- Notable: no code changes were required — the phase implementation was correct as left; no live infrastructure touched.
- Next pending phase: `151_pdf_image_extraction` in `.agents/phases/todo/`.
This commit is contained in:
2026-10-02 02:35:03 -04:00
parent 9d4c813cae
commit edf6048a0d
59 changed files with 2719 additions and 476 deletions
+27 -21
View File
@@ -100,8 +100,9 @@ Test → observable mapping (Playwright Mapping Rule):
``.sync-label-main`` == ``Syncing… chunkprog/big.md``,
``.sync-label-count`` == ``(1/2)``, and ``.sync-label-chunk``
VISIBLE matching ``· chunk \\d+/\\d+`` with the total == the
expected chunk count — and the button ``title`` (and
``#sync-result``, the aria-live announcer) carries the UNTRUNCATED
expected chunk count — and the button ``title`` (and the
``#sync-progress`` box — the aria-live announcer, phase 150: the
box is the single sync text surface) carries the UNTRUNCATED
concatenation main + " " + count + " " + chunk (A4 — asserted
atomically in the same in-browser evaluation, so the parts + title
can never straddle a poll tick); after the file's embed span the
@@ -110,8 +111,8 @@ Test → observable mapping (Playwright Mapping Rule):
hidden + empty while the label itself keeps rendering in sync
(the title tracks the rendered parts, A4); on settle the button
reads the terminal ``Synced HH:MM`` (the phase-32 contract) with
the count + chunk parts hidden and ``#sync-result`` carrying the
counts line.
the count + chunk parts hidden and the ``#sync-progress`` box
carrying the counts line (phase 150).
"""
from __future__ import annotations
@@ -314,7 +315,8 @@ def _dedup_consecutive(values: list[int]) -> list[int]:
#: untruncated A4 text — the big file is the first of the two, so the
#: count part is "(1/2)"), the pinned chunk part is visible matching
#: "· chunk n/m" with m == the expected chunk count (the arg), and the
#: button title + #sync-result carry the UNTRUNCATED concatenation
#: button title + the #sync-progress box (the aria-live announcer,
#: phase 150) carry the UNTRUNCATED concatenation
#: (main + " " + count + " " + chunk — A4: the announcer hears the
#: whole "Syncing… <file> (1/2) · chunk n/m").
_JS_IMPORT_CHUNK_SNAPSHOT = """
@@ -332,14 +334,14 @@ _JS_IMPORT_CHUNK_SNAPSHOT = """
if (!m || Number(m[2]) !== n) return null;
const full = main.textContent + ' ' + count.textContent + ' ' + chunk.textContent;
if (btn.getAttribute('title') !== full) return null;
const result = document.querySelector('#sync-result');
if (!result || result.textContent !== full) return null;
const boxText = document.querySelector('#sync-progress-text');
if (!boxText || boxText.textContent !== full) return null;
return {
mainText: main.textContent,
countText: count.textContent,
chunkText: chunk.textContent,
title: btn.getAttribute('title'),
resultText: result.textContent,
boxText: boxText.textContent,
};
}
"""
@@ -874,15 +876,16 @@ def test_label_shows_the_chunk_part(
and the VISIBLE chunk part matching ``· chunk n/m`` with the total
== the expected chunk count (the big file's span is ~45 s under
the slow proxy — the poll cannot miss it) — with the button
``title`` + ``#sync-result`` carrying the UNTRUNCATED
concatenation (A4, asserted atomically in the same in-browser
evaluation). After the file's embed span, once the label has left
the import phase (overview / summaries / terminal), the chunk part
is CLEARED — hidden + empty — while the label keeps rendering in
sync (the title tracks the rendered parts); on settle the button
reads the terminal ``Synced HH:MM`` (the phase-32 contract) with
the count + chunk parts hidden and ``#sync-result`` carrying the
counts line."""
``title`` + the ``#sync-progress`` box (the aria-live announcer,
phase 150) carrying the UNTRUNCATED concatenation (A4, asserted
atomically in the same in-browser evaluation). After the file's
embed span, once the label has left the import phase (overview /
summaries / terminal), the chunk part is CLEARED — hidden + empty
— while the label keeps rendering in sync (the title tracks the
rendered parts); on settle the button reads the terminal
``Synced HH:MM`` (the phase-32 contract) with the count + chunk
parts hidden and the ``#sync-progress`` box carrying the counts
line (phase 150)."""
page.set_default_timeout(30_000)
big_content = (chunkprog_dir / "big.md").read_text(encoding="utf-8")
n_chunks = len(_expected_big_chunks(big_content))
@@ -893,8 +896,8 @@ def test_label_shows_the_chunk_part(
# Step 1 — the import span with the chunk part (the page's own 2 s
# poll; the big file's ~45 s span outlives the poll ~20×). The
# in-browser snapshot verifies the parts + the title +
# #sync-result TOGETHER (one JS task per poll tick — the atomic
# check can never straddle one) and returns the texts.
# #sync-progress-text TOGETHER (one JS task per poll tick — the
# atomic check can never straddle one) and returns the texts.
snap = page.wait_for_function(
_JS_IMPORT_CHUNK_SNAPSHOT, arg=n_chunks, timeout=SETTLE_TIMEOUT_MS
).json_value()
@@ -908,7 +911,9 @@ def test_label_shows_the_chunk_part(
# aria-live announcer (the "· chunk" part included).
full = f"Syncing… {SOURCE}/big.md (1/2) {snap['chunkText']}"
assert snap["title"] == full and "· chunk" in snap["title"], snap
assert snap["resultText"] == full, snap
# The box IS the announcer (phase 150) — it carries the same full
# string the title does.
assert snap["boxText"] == full, snap
# Step 2 — after the file's embed span: the chunk part is CLEARED
# (hidden + empty) once the label has left the import phase — the
@@ -942,5 +947,6 @@ def test_label_shows_the_chunk_part(
expect(page.locator(".sync-label-count")).to_have_text("")
expect(page.locator(".sync-label-chunk")).to_be_hidden()
expect(page.locator(".sync-label-chunk")).to_have_text("")
expect(page.locator("#sync-result")).to_have_text(f"{N_FILES} added")
# The counts line rides the box (phase 150 — the sole sync surface).
expect(page.locator("#sync-progress-text")).to_have_text(f"{N_FILES} added")
expect(page.locator("#sync-error-banner")).to_be_hidden()