Files
brain-of-reese/tests/unit/test_sync_scan_label_ui.py
T
ducoterra a45f744604
Build and Push Containers / build-and-push-app (push) Successful in 2m5s
Build and Push Containers / build-and-push-db (push) Successful in 13s
phase: 194_sync_pending_count
**Phase 194 — final verification pass: all green.**

**Verified (all 5 tasks already complete; nothing re-implemented needed)**
- `app/rag/importer.py`: `_prescan_pending` + `_pending_hash_stmt` (scalar `documents.file_hash`, own session, ONE `asyncio.to_thread` span, REPLACES the stats pre-walk), `on_scan_progress`, `progress` pair = pending position, `0` branch = byte-identical phase-193 walk.
- `app/config.py`/`settings_catalog.py`/`.env.example`: `sync_pending_count=1` + validator; `app/api/sync.py`: `"scan"` phase, `scan_done`/`scan_total` (additive after `files_total`, kept at every terminal), forced/throttled RSS split; `frontend/assets/sources.js`: `fmtSyncPhaseLabel` scan branch first-arm, `fmtSyncLabel` untouched.

**Tests / lint / coverage (exact commands)**
- `uv run pytest --cov=app --cov-report=term-missing` → **4835 passed**, `app/` 99% (7240 stmts / 16 miss), exit 0
- `uv run pytest tests/e2e/test_sync_pending_count.py -v --no-cov` → **8 passed in 150.81s** (phase suite, isolated)
- 14 sync-lineage E2E suites, one pytest process each (`test_sync_remaining_count`, `…_chunk_progress`, `…_progress_box`, `…_peak_memory_visible`, `…_label_mobile`, `…_upload_progress`, `test_archive_upload_sources`, `…_image_progress`, `…_summary_visibility`, `…_status_freshness`, `test_smoke`, `test_fast_path_deferred_content`, `test_sync_session_reset`, `test_sync_git_freeze`) → **all exit 0**
- `uv run ruff check .` → All checks passed; `uv run pyright` → **0 errors**

**Completion criteria** — all met: `N remaining` = predicted change set (E2E arms 1/2/5, first import counts the walk, no-change → no count part); `scan` phase visible + flips to `import`, scan pair reset/kept at terminals (`test_sync_scan_phase.py`); `BOR_SYNC_PENDING_COUNT=0` walk semantics ("7 remaining" arm) + **negative refuses startup naming the field (verified live by importing `app.main`)**; pre-scan off-loop + cancel lands `cancelled` (integration pins); CLI/upload byte-identical (unedited pins green); payload additive (`_PRE_187_STATUS_KEYS | _MEMORY_KEYS | _SCAN_KEYS`); suite green, coverage >90%, lint/types clean.

**Documentation** — 3 fixes: README sync "States" bullet had an **unclosed parenthesis** (pre-existing, 193/194) and the scan-label "total is 0" clause was ambiguous about a `=0` run (it still counts the walk once the import starts); **AGENTS.md rule 17 had a stray `)`** (pre-existing from phase 191). Also verified consistency both directions for all 87 settings ↔ README ↔ `.env.example` ↔ settings catalog, the pages table ↔ `app/main.py` routes, no stale `BOR_` names, and README/AGENTS claims through phase 194.

**Notes/deviations** — no live-infra changes (prod sync knob ships default-on, no deployment touched); no git index/history commands run (all changes left in the working tree for the harness); commit criterion left to the harness as instructed.

**Next pending phase:** none — `.agents/phases/todo/` holds only `194_sync_pending_count`, which is now ready to move to `complete/`.
2026-10-20 06:36:21 -04:00

545 lines
24 KiB
Python

"""Phase 194, task 03 — the Sources page renders the scan as a scan.
The owner report the whole phase exists for (2026-10-19): *"1216
remaining"* on a ~10,000-file KB that had ~10 files to sync. Task 01 moved
the server's ``done``/``total`` to the pre-scan's predicted change set and
task 02 exposed the scan as a phase (``phase: "scan"`` + ``scan_done`` /
``scan_total``). This module pins the UI half (D4, owner-confirmed A2):
``fmtSyncPhaseLabel`` gains ONE explicit ``"scan"`` branch that says
``Scanning…`` and puts the scan's own ``(n/total)`` in the SAME count slot
the import phase's ``N remaining`` uses — so the walk position surviving as
an ``n/m`` is honest, because the word in front of it is "Scanning" and
never "Syncing" (TODO L6's objection was the sync count wearing the
total's shape).
Pinned here (source markers, plus one node run of the REAL label path):
* the branch — ``status.phase === "scan"`` is in the decision tree BEFORE
``overview`` / ``summaries`` / the fall-through, its main text is exactly
``Scanning…``, and it never reads ``current_file`` (null in the phase),
the file pair, the chunk pair, the image pair or the summaries pair;
* the shape — the count part is ``(${status.scan_done}/${status.scan_total})``
landing on the ``count`` slot (``setSyncLabel``'s hidden-toggle + the A4
untruncated button-title / ``#sync-progress`` placement carry it with no
new element), with the phase-64 ``total === 0`` guard (the empty KB and
the scan's own first frame render the bare ``Scanning…``) and no
chunk/image part (the scan reads no file);
* the pass-through — both sync status call sites (the poll tick, the
load-time re-attach) forward the WHOLE status object, which is what
carries the scan pair to the wrapper (the summaries / chunk / image
pattern: ``phase`` selects which pair renders, no field is re-listed);
* the UNTOUCHED set — ``fmtSyncLabel``'s ``N remaining`` expression
verbatim (the phase-193 pin), the chunk / image / summaries part
builders, ``setSyncLabel``'s composed full string, ``fmtSyncResult`` and
``fmtSyncPeakClause`` (the phase-187 trailing clause lands on the scan
label's line unchanged, and stays a terminal-only clause);
* the docstrings — the decision-tree paragraph names the new branch,
narrows the fallback to the null prelude + ``"import"``, and keeps the
phase-193 ``N remaining`` clause.
The owner-visible arms (the count-down over a real incremental sync, the
scan label in a real browser) live in
``tests/e2e/test_sync_pending_count.py`` (task 05); the stale pinned suites
retarget in task 04.
"""
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
import pytest
#: The node behavior check needs node (the test-document-viewer pattern).
HAVE_NODE = shutil.which("node") is not None
ROOT = Path(__file__).resolve().parents[2]
SOURCES_JS = ROOT / "frontend" / "assets" / "sources.js"
def _js() -> str:
return SOURCES_JS.read_text(encoding="utf-8")
def _fn(js: str, name: str) -> str:
"""The source of the first ``function <name>`` (brace balanced — the
house pin pattern from tests/unit/test_frontend_sync_upload.py: the
functions live inside ``mount(root)``, so the closing brace is
indented)."""
fn = js.find(f"function {name}")
assert fn != -1, f"{name} must be defined in sources.js"
open_idx = js.find("{", fn)
depth = 0
for i in range(open_idx, len(js)):
c = js[i]
if c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
return js[fn : i + 1]
raise AssertionError(f"unbalanced braces in {name}")
def _doc_above(js: str, name: str) -> str:
"""The block comment immediately above ``function <name>``."""
i = js.find(f"function {name}")
assert i != -1, f"{name} must be defined in sources.js"
start = js.rfind("/*", 0, i)
assert start != -1, f"{name} must carry a docstring"
end = js.find("*/", start)
assert start < end < i, f"{name}'s docstring must sit above the function"
return js[start:end]
def _scan_slice(js: str) -> str:
"""The ``"scan"`` branch of ``fmtSyncPhaseLabel`` — from its ``if`` to
the ``overview`` branch that follows it (every shape pin below reads
ONLY this slice, so a shape that leaked into another branch is caught
and a branch that moved is caught)."""
body = _fn(js, "fmtSyncPhaseLabel")
start = body.find('status.phase === "scan"')
assert start != -1, 'the "scan" branch is missing from fmtSyncPhaseLabel'
end = body.find('status.phase === "overview"', start)
assert end != -1, "the overview branch must follow the scan branch"
return body[start:end]
# ---------- the branch ----------
def test_scan_branch_is_the_first_arm_of_the_decision_tree() -> None:
"""The wrapper gains ONE explicit ``"scan"`` branch (D4): the phase
comparison sits BEFORE ``overview``, ``summaries`` and the
``fmtSyncLabel`` fall-through (the machine order is ``null`` →
``"scan"`` → ``"import"`` → ``"overview"`` → ``"summaries"``, and
keeping the arms in that order is what leaves every pre-existing
branch slice byte-identical), and the main text is exactly
``Scanning…`` with the ellipsis the other running labels use."""
js = _js()
body = _fn(js, "fmtSyncPhaseLabel")
i_scan = body.find('status.phase === "scan"')
i_ov = body.find('status.phase === "overview"')
i_sum = body.find('status.phase === "summaries"')
i_fall = body.find("fmtSyncLabel(", i_sum)
assert -1 < i_scan < i_ov < i_sum < i_fall, (
"the scan branch must be the first arm, ahead of overview / "
"summaries / the null-prelude + import fall-through"
)
assert 'main: "Scanning\u2026"' in _scan_slice(js), (
"the main text is the bare Scanning label — no file"
)
def test_scan_branch_reads_the_scan_pair_and_nothing_else() -> None:
"""The branch consumes the scan pair the way it already consumes the
summaries pair (``phase`` selects which pair renders). It NEVER reads
``current_file`` — the runner keeps it null through the phase, because
the scan names no file — nor the file pair, the chunk pair, the image
pair or the summaries pair (a scan embeds no chunk and describes no
image, so those fields carry nothing meaningful here)."""
scan = _scan_slice(_js())
assert "status.scan_done" in scan and "status.scan_total" in scan, (
"the count part is the status's own scan pair"
)
for field in (
"current_file",
"files_done",
"files_total",
"chunks_total",
"current_chunk",
"images_total",
"images_done",
"summaries_total",
"summaries_done",
"current_summary",
):
assert field not in scan, (
f'the "scan" branch must not read {field} — that field is the '
"import/overview/summaries phase's own position"
)
def test_scan_branch_emits_no_chunk_and_no_image_part() -> None:
"""The scan names no file, so its parts are the main text + the count
only: ``chunk: ""`` and ``image: ""`` (the writer hides both spans and
keeps them out of the composed full string — the overview branch's
shape, phase 98/152)."""
scan = _scan_slice(_js())
assert 'chunk: ""' in scan, "the scan label carries no chunk part"
assert 'image: ""' in scan, "the scan label carries no image part"
# ---------- the (n/total) count part ----------
def test_scan_count_part_is_the_pair_in_the_count_slot() -> None:
"""The count part is the exact ``(${status.scan_done}/${status.scan_total})``
template — a plain ``(n/total)``, no thousands separator, no suffix —
and it lands on the ``count`` key, i.e. the SAME slot the import
phase's ``N remaining`` uses (``setSyncLabel``'s
``.sync-label-count`` span + the composed full string), so the count
stays pinned on a phone and the A4 untruncated label (button title +
``#sync-progress``) carries ``Scanning… (n/total)`` with no new
element and no new live region."""
scan = _scan_slice(_js())
assert "`(${status.scan_done}/${status.scan_total})`" in scan, (
"the exact (n/total) shape over the scan pair"
)
i_count = scan.find("count:")
i_template = scan.find("`(${status.scan_done}/${status.scan_total})`")
assert -1 < i_count < i_template, "the pair renders INTO the count slot"
assert "remaining" not in scan, (
"the scan never says remaining — that part belongs to fmtSyncLabel"
)
for helper in ("toLocaleString", ".toFixed"):
assert helper not in scan, "plain integers, the house label style"
def test_scan_count_part_hides_while_the_total_is_zero() -> None:
"""The phase-64 ``total === 0`` contract, extended to the scan: while
the scan has not reported its denominator (and for the degenerate
empty KB, where the importer fires no scan callback at all) the count
part is the empty string — the bare ``Scanning…`` renders, never
``(0/0)``. The guard precedes the template and the false arm is the
empty string."""
scan = _scan_slice(_js())
i_guard = scan.find("status.scan_total > 0")
i_template = scan.find("`(${status.scan_done}/${status.scan_total})`")
i_else = scan.find(': ""', i_template)
assert -1 < i_guard < i_template < i_else, (
"scan_total > 0 ? (n/total) : \"\" — the phase-64 no-count-part guard"
)
# ---------- the caller pass-through ----------
def test_poll_tick_forwards_the_whole_status_so_the_scan_pair_rides() -> None:
"""The running-sync branch of the poll tick passes the WHOLE status
object as ``enterSyncRunningState``'s fifth argument — that object IS
the pass-through for the scan pair, exactly like the summaries /
chunk / image pairs (the call site names it). A status with
``phase: "scan"`` therefore reaches the wrapper with its
``scan_done`` / ``scan_total`` intact."""
js = _js()
start = js.find("function startSyncPolling")
assert start != -1
tick = js[start : js.find("async function startSync", start)]
i_run = tick.find('syncStatus.state === "running"')
assert i_run != -1
# the call-site comment sits directly above the branch — it is part of
# the pass-through's documentation, so the slice starts there
branch = tick[tick.rfind("// 1. sync running", 0, i_run) : tick.find(
'uploadStatus && uploadStatus.state === "running"', i_run
)]
assert (
'"sync", syncStatus.current_file, syncStatus.files_done, syncStatus.files_total'
in branch
), "the pre-phase positional args are unchanged"
i_args = branch.find("syncStatus.files_total")
i_pass = branch.find("syncStatus\n", i_args)
assert i_pass != -1, "the WHOLE syncStatus object rides in as the 5th arg"
assert "scan_done" in branch and "scan_total" in branch, (
"the call-site comment names the scan pair riding the status (phase 194)"
)
def test_load_time_reattach_forwards_the_whole_status_too() -> None:
"""The load-time re-attach is the other sync call site: a reload
during the pre-scan re-enters running state with the WHOLE status, so
the re-attached label reads ``Scanning… (n/total)`` (the never-stale
contract — phase 98's mid-summaries reload, now also mid-scan)."""
js = _js()
body = _fn(js, "initSyncButton")
assert (
'"sync", status.current_file, status.files_done, status.files_total' in body
), "the pre-phase positional args are unchanged"
i_args = body.find("status.files_total")
i_pass = body.find("status\n", i_args)
assert i_pass != -1, "the WHOLE status object rides in as the 5th arg"
doc = _doc_above(js, "initSyncButton")
assert "phase 194" in doc and "Scanning" in doc, (
"the re-attach docstring names the scan label it can re-enter (194)"
)
def test_running_entry_hands_the_status_to_the_phase_wrapper() -> None:
"""``enterSyncRunningState`` keeps the phase-aware split untouched:
sync + status → ``fmtSyncPhaseLabel(status)`` (the wrapper that now
owns the scan branch), everything else → the bare ``fmtSyncLabel``
parts, and the parts still go to the ONE writer with the running
flag (so the scan label lands on the button title + the box)."""
entry = _fn(_js(), "enterSyncRunningState")
assert "fmtSyncPhaseLabel(status)" in entry
assert 'kind === "sync" && status' in entry
assert "setSyncLabel(main, count, chunk, true, image)" in entry
# ---------- the UNTOUCHED set ----------
def test_fmt_sync_label_n_remaining_arithmetic_is_byte_identical() -> None:
"""The phase-193 count-down builder is UNTOUCHED by this task — the
server-side semantic change (the pair becomes the pending position)
flows through the SAME expression. The exact line, both guards and the
plain-integer shape, plus the absence of anything scan-shaped in this
builder."""
bare = _fn(_js(), "fmtSyncLabel")
assert (
"const count = total > 0 && total - done > 0"
" ? `${total - done} remaining` : \"\";"
) in bare, "the phase-193 count expression, byte for byte"
assert 'const prefix = kind === "upload" ? "Importing" : "Syncing\u2026";' in bare
assert "return { main, count }" in bare
for word in ("scan", "chunk", "image", "summaries", "phase"):
assert word not in bare, f"fmtSyncLabel stays free of {word!r}"
def test_other_phase_labels_and_the_writers_are_untouched() -> None:
"""The regression pins for everything the scan branch could have
disturbed: the overview copy, the summaries folder + ``(n/m)``
position, the import fall-through's chunk + image parts, the single
writer's signature + composed full string, the terminal counts line
(no running count part, no scan word) and the phase-187 peak clause
(still a terminal-only trailing clause, null renders nothing)."""
js = _js()
phase = _fn(js, "fmtSyncPhaseLabel")
assert (
'return { main: "Writing KB overview\u2026", count: "", chunk: "", image: "" };'
in phase
)
assert "`Summarizing folders\u2026${folder}`" in phase
assert (
"count: `(${status.summaries_done}/${status.summaries_total})`" in phase
), "the summaries folder position keeps its (n/m)"
assert (
'const { main, count } = fmtSyncLabel(\n'
' "sync", status.current_file, status.files_done, status.files_total\n'
" );" in phase
), "the import fall-through still calls the bare builder unchanged"
assert "`\u00b7 chunk ${status.current_chunk}/${status.chunks_total}`" in phase
assert "`\u00b7 image ${status.images_done}/${status.images_total}`" in phase
assert "return { main, count, chunk, image };" in phase
writer = _fn(js, "setSyncLabel")
assert (
'function setSyncLabel(mainText, countText = "", chunkText = "",'
' running = false, imageText = "")' in writer
)
assert "syncLabelCount.textContent = countText" in writer
assert "syncLabelCount.hidden = false" in writer
assert (
"const full =\n"
" mainText +\n"
" (countText ? ` ${countText}` : \"\") +\n"
" (chunkText ? ` ${chunkText}` : \"\") +\n"
" (imageText ? ` ${imageText}` : \"\");" in writer
), "the A4 untruncated label is still main + count + chunk + image"
assert "if (running) setSyncProgress(full);" in writer
assert "syncBtn.title = full" in writer
result = _fn(js, "fmtSyncResult")
for word in ("remaining", "Scanning", "scan"):
assert word not in result, f"the terminal counts line stays {word!r}-free"
peak = _fn(js, "fmtSyncPeakClause")
assert "return ` \u00b7 peak ${peak} MB`;" in peak
assert "status.rss_peak_mb ?? status.rss_mb" in peak
assert "Scanning" not in peak, "the clause is terminal-only, unchanged"
# ---------- the docstrings ----------
def test_phase_label_docstring_names_the_branch_and_narrows_the_fallback() -> None:
"""The decision-tree paragraph above the wrapper names the new arm and
narrows the fall-through to the two states that remain, keeps the
phase-193 ``N remaining`` clause, states the ``total === 0`` guard,
and says WHY an ``(n/total)`` is honest here (TODO L6: the objection
was the sync count wearing the total's shape — this label says
Scanning, never Syncing)."""
doc = _doc_above(_js(), "fmtSyncPhaseLabel")
assert '"scan"' in doc and "Scanning\u2026" in doc, "the new branch is named"
assert "scan_done" in doc and "scan_total" in doc, "the pair is named"
assert "scan_total === 0" in doc, "the no-count-part guard is documented"
assert "ONLY the two" in doc and '"import"' in doc, "the fallback is narrowed"
assert "`N remaining`" in doc and "N = total - done" in doc, (
"the phase-193 count-down clause survives the rewrite"
)
assert "never" in doc and '"Syncing"' in doc, (
"the honesty clause: Scanning, never Syncing (TODO L6)"
)
assert "A4" in doc, "the untruncated placement is still documented"
def test_module_and_section_headers_document_the_scan_label() -> None:
"""The module header carries the phase-194 paragraph (the count is the
WORK, the scan says so), and the sync section's decision tree names
the ``"scan"`` arm with its ``(n/total)`` — the header the wrapper's
docstring points at."""
js = _js()
header = js[: js.find("import { fetchIsAdmin }")]
assert "Phase 194 (task 03)" in header, "the module header names the phase"
assert "Scanning\u2026 (n/total)" in header, "the module header names the label"
marker = js.find("Sync sources button (Sources page only)")
assert marker != -1
section = js[marker : js.find("const syncBtn", marker)]
assert "Phase 194 task 03" in section, "the section header names the phase"
assert "Scanning\u2026 (n/total)" in section, "the decision tree names the label"
assert "scan_done" in section, "the pair the WHOLE status carries is named"
# ---------- the REAL label path under node ----------
#: The stub element refs (the test-document-viewer pattern; the same
#: shapes tests/unit/test_frontend_sync_upload.py drives). The label
#: writer, the box writer and the cancel control are the REAL functions.
_NODE_HEAD = """
const syncLabelMain = { textContent: "" };
const syncLabelCount = { textContent: "", hidden: true };
const syncLabelChunk = { textContent: "", hidden: true };
const syncProgress = { hidden: true };
const syncProgressText = { textContent: "" };
const syncBtn = {
disabled: false,
title: "",
attrs: {},
setAttribute(k, v) { this.attrs[k] = v; },
removeAttribute(k) { delete this.attrs[k]; },
classes: new Set(),
classList: {
add(c) { syncBtn.classes.add(c); },
remove(c) { syncBtn.classes.delete(c); },
has(c) { return syncBtn.classes.has(c); },
},
};
const syncIcon = {
classes: new Set(),
classList: {
add(c) { syncIcon.classes.add(c); },
remove(c) { syncIcon.classes.delete(c); },
has(c) { return syncIcon.classes.has(c); },
},
};
const syncCancelBtn = {
hidden: true,
disabled: true,
attrs: {},
setAttribute(k, v) { this.attrs[k] = v; },
removeAttribute(k) { delete this.attrs[k]; },
};
"""
_NODE_TAIL = """
const show = (tag) => {
console.log(JSON.stringify([
tag,
syncLabelMain.textContent,
syncLabelCount.textContent,
syncLabelCount.hidden,
syncBtn.title,
syncProgressText.textContent,
syncBtn.attrs['aria-label'] || '',
]));
};
// 1. the scan, denominator known + the position climbing (the mid-scan poll):
enterSyncRunningState("sync", null, 0, 0, {
phase: "scan", current_file: null, files_done: 0, files_total: 0,
scan_done: 3, scan_total: 9, current_chunk: 0, chunks_total: 0,
});
show("scan-3-9");
// 2. the scan's degenerate arm: an empty KB / the scan's own first frame
// (0/0) — the phase-64 guard, the bare label with NO count part:
enterSyncRunningState("sync", null, 0, 0, {
phase: "scan", current_file: null, files_done: 0, files_total: 0,
scan_done: 0, scan_total: 0, current_chunk: 0, chunks_total: 0,
});
show("scan-0-0");
// 3. the prelude (null phase) is untouched — bare "Syncing\u2026":
enterSyncRunningState("sync", null, 0, 0, {
phase: null, current_file: null, files_done: 0, files_total: 0,
scan_done: 4, scan_total: 9, current_chunk: 0, chunks_total: 0,
});
show("prelude");
// 4. the import phase is untouched — the phase-193 count-down + the chunk
// part, and the scan pair present on the payload but NOT rendered:
enterSyncRunningState("sync", "src/a/b.md", 2, 9, {
phase: "import", current_file: "src/a/b.md", files_done: 2, files_total: 9,
scan_done: 9, scan_total: 9, current_chunk: 14, chunks_total: 38,
});
show("import");
// 5. the overview + summaries labels are untouched:
enterSyncRunningState("sync", null, 9, 9, {
phase: "overview", current_file: null, files_done: 9, files_total: 9,
scan_done: 9, scan_total: 9, current_chunk: 0, chunks_total: 0,
});
show("overview");
enterSyncRunningState("sync", null, 9, 9, {
phase: "summaries", current_file: null, files_done: 9, files_total: 9,
scan_done: 9, scan_total: 9, current_summary: "src/alpha",
summaries_done: 2, summaries_total: 3, current_chunk: 0, chunks_total: 0,
});
show("summaries");
// 6. the 202/409 click (no status at all) stays bare:
enterSyncRunningState("sync", null, 0, 0);
show("click");
"""
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_scan_label_renders_through_the_real_label_path_under_node() -> None:
"""Behavior, not just markers: the REAL extracted functions run
against stub refs. ``phase: "scan"`` renders main ``Scanning…`` with
the count ``(3/9)`` visible, and the SAME string lands on the button
title, the box (the sole aria-live region) and the button's
accessible name (A4) — while every other phase renders exactly what it
rendered before this task."""
js = _js()
fns = "\n".join(
_fn(js, name)
for name in (
"fmtSyncLabel",
"fmtSyncPhaseLabel",
"setSyncLabel",
"setSyncProgress",
"showSyncCancelBtn",
"hideSyncCancelBtn",
"enterSyncRunningState",
)
)
out = subprocess.run(
["node", "-e", _NODE_HEAD + fns + _NODE_TAIL],
capture_output=True,
text=True,
timeout=60,
)
assert out.returncode == 0, out.stderr
rows = {row[0]: row for row in (json.loads(line) for line in out.stdout.splitlines())}
main, count, hidden, title, box, aria = rows["scan-3-9"][1:]
assert main == "Scanning\u2026", rows["scan-3-9"]
assert count == "(3/9)" and hidden is False, rows["scan-3-9"]
assert title == "Scanning\u2026 (3/9)" == box == aria, (
f"A4: the untruncated scan label rides title + box + aria: {rows['scan-3-9']}"
)
main, count, hidden, title, box, aria = rows["scan-0-0"][1:]
assert main == "Scanning\u2026" and count == "" and hidden is True, rows["scan-0-0"]
assert title == "Scanning\u2026" == box == aria, (
f"0/0 renders NO count part (the empty-KB guard): {rows['scan-0-0']}"
)
# every other state is byte-identical to its pre-phase label
assert rows["prelude"][1] == "Syncing\u2026" and rows["prelude"][2] == ""
assert rows["prelude"][4] == "Syncing\u2026", "a scan pair never renders in the prelude"
assert rows["import"][1] == "Syncing\u2026 src/a/b.md"
assert rows["import"][2] == "7 remaining", rows["import"]
assert rows["import"][4] == "Syncing\u2026 src/a/b.md 7 remaining \u00b7 chunk 14/38"
assert rows["overview"][1] == "Writing KB overview\u2026" and rows["overview"][2] == ""
assert rows["summaries"][1] == "Summarizing folders\u2026 src/alpha"
assert rows["summaries"][2] == "(2/3)", "the summaries position keeps its (n/m)"
assert rows["click"][1] == "Syncing\u2026" and rows["click"][2] == ""