phase: 129_sync_chunk_progress
Build and Push Containers / build-and-push-app (push) Successful in 8m23s
Build and Push Containers / build-and-push-db (push) Successful in 40s

All green. Final report:

**Phase 129 final verification pass — 100% green** (previous work intact; this pass re-verified everything and fixed two pre-existing README inconsistencies)

- Verified on-spec: `LLMClient.embed` per-batch `on_progress` hook; importer `on_chunk_progress` plumbing (text/image/PDF branches; summary embed excluded); `SyncStatus.current_chunk/chunks_total` reset+clear contract; UI `.sync-label-chunk` pinned part with untruncated `title`/`#sync-result`
- `uv run pytest --cov=app -q`: exit 0, ~3041 passed, 0 failed, `app/` coverage **99%** (>90% gate)
- `uv run pytest tests/e2e/test_sync_chunk_progress.py -v --no-cov` (isolated, DB up): **2 passed** (endpoint chunk climb with true `chunk_document` total + UI `· chunk n/m` part)
- `uv run ruff check .`: all checks passed · `uv run pyright`: 0 errors, 0 warnings
- Completion criteria: all 5 ✅ (incl. no behavior change in completed phases — full suite green, CLI/chat no-op paths byte-identical)
- Docs: README.md fixed 2 pre-existing inconsistencies (upload section claimed "unpacks + scans immediately" — phase 90 made it unpack+register only, scan deferred to Sync; troubleshooting claimed "120s client timeout" — actual guard is the 300 s pre-token `TURN_TIMEOUT_MS`). Phase-129 label behavior already documented. AGENTS.md re-verified consistent — no changes
- Removed stray untracked `NOTES.md` (8-byte artifact); no live-infra changes; no commits — all changes left in working tree for the harness
- Next pending phase: `130_chat_answer_clusters`
This commit is contained in:
2026-09-28 03:38:14 -04:00
parent 1139f007cf
commit 2d0f608246
53 changed files with 3590 additions and 157 deletions
+311 -24
View File
@@ -63,12 +63,39 @@ visible span carries main + count, the space is the CSS gap). Every
other label state (idle "Sync sources", settle "Synced HH:MM", the
202/409 click, the re-attach) goes through setSyncLabel(main) —
count hidden.
Phase 129 (task 04) adds the label's THIRD pinned part — the chunk
position: #sync-label ships a hidden .sync-label-chunk span after the
count span (the pinned "· chunk n/m" — the in-flight file's
embed-batch position, batch granularity: n the 1-based first chunk of
the in-flight batch, m the file's total content chunks). The ONLY
builder that reads the status's chunk fields is fmtSyncPhaseLabel's
import fall-through — "· chunk n/m" renders while chunks_total > 0
and hides in every other state (the prelude, the 202/409 click, the
upload run — its status has no chunk fields, overview, summaries —
its own (n/m) is its position, idle / settle). setSyncLabel(mainText,
countText, chunkText) lands the part under the same present/absent
contract as the count part, and the UNTRUNCATED full label — main +
" " + count + " " + chunk — rides the button title + #sync-result
(A4 — the in-flight import reads "Syncing… <file> (2/3) · chunk
14/38"). The part keeps the pinned mono CSS (flex 0 0 auto, nowrap,
--mono, the rule right after .sync-label-count). A node harness (the
test-document-viewer pattern) behaviorally pins the rendered parts +
the full title across every label state.
"""
from __future__ import annotations
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
#: The node harness needs node (the test-document-viewer pattern).
HAVE_NODE = shutil.which("node") is not None
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
SOURCES_JS = FRONTEND / "assets" / "sources.js"
STYLES_CSS = FRONTEND / "assets" / "styles.css"
@@ -219,21 +246,26 @@ def test_fmt_sync_label_returns_the_parts_contract() -> None:
def test_set_sync_label_is_the_single_parts_writer() -> None:
"""setSyncLabel(mainText, countText = "") — Phase 128 (task 01):
the SINGLE point that writes #sync-label. The MAIN part gets
mainText (textContent only — the house rule, no HTML strings);
the COUNT part gets countText when present (textContent +
hidden=false — the "(n/m)" position is visible, NEVER clipped) and
is emptied + hidden=true when absent (the count span is hidden
whenever there is no "(n/m)" to show). Then the UNTRUNCATED full
label — mainText + (countText ? " " + countText : "") — rides the
button title + #sync-result (the aria-live announcer) exactly as
before (A4 — the screen reader hears the whole thing)."""
"""setSyncLabel(mainText, countText = "", chunkText = "") — Phase
128 (task 01) + Phase 129 (task 04): the SINGLE point that writes
#sync-label. The MAIN part gets mainText (textContent only — the
house rule, no HTML strings); the COUNT part gets countText when
present (textContent + hidden=false — the "(n/m)" position is
visible, NEVER clipped) and is emptied + hidden=true when absent;
the CHUNK part (phase 129) gets chunkText under the SAME contract
(present → the pinned "· chunk n/m" is visible, absent → emptied
+ hidden=true — the part renders only while a file's embed batch
is in flight). Then the UNTRUNCATED full label — mainText +
(countText ? " " + countText : "") + (chunkText ? " " + chunkText
: "") — rides the button title + #sync-result (the aria-live
announcer) (A4 — the screen reader hears the whole "Syncing…
<file> (2/3) · chunk 14/38")."""
body = _fn(_js(), "setSyncLabel")
assert 'function setSyncLabel(mainText, countText = "")' in body
assert 'function setSyncLabel(mainText, countText = "", chunkText = "")' in body
# the parts: textContent only (the house rule), null-guarded refs
assert "syncLabelMain.textContent = mainText" in body
assert "syncLabelCount.textContent = countText" in body
assert "syncLabelChunk.textContent = chunkText" in body
# present count → visible; absent count → hidden, emptied
assert "syncLabelCount.hidden = false" in body
assert 'syncLabelCount.textContent = ""' in body
@@ -241,11 +273,22 @@ def test_set_sync_label_is_the_single_parts_writer() -> None:
i_vis = body.find("syncLabelCount.hidden = false")
i_hid = body.find("syncLabelCount.hidden = true")
assert -1 < i_vis < i_hid, "the present-count branch precedes the absent one"
# present chunk → visible; absent chunk → hidden, emptied (same contract)
assert "syncLabelChunk.hidden = false" in body
assert 'syncLabelChunk.textContent = ""' in body
assert "syncLabelChunk.hidden = true" in body
i_cvis = body.find("syncLabelChunk.hidden = false")
i_chid = body.find("syncLabelChunk.hidden = true")
assert -1 < i_cvis < i_chid, "the present-chunk branch precedes the absent one"
# A4: the untruncated full label rides the title + the announcer —
# main + " " + count (the join space), bare main when no count.
# main + " " + count + " " + chunk (the join spaces = the CSS
# gaps), bare main when both are absent.
assert (
'const full = countText ? `${mainText} ${countText}` : mainText'
) in body, "the full label is main + ' ' + count (bare main when absent)"
"const full =\n"
" mainText +\n"
" (countText ? ` ${countText}` : \"\") +\n"
" (chunkText ? ` ${chunkText}` : \"\");"
) in body, ("the full label is main + ' ' + count + ' ' + chunk (spaces = the CSS gaps)")
i_full = body.find("const full")
i_title = body.find("syncBtn.title = full")
i_result = body.find("syncResult.textContent = full")
@@ -281,8 +324,11 @@ def test_fmt_sync_phase_label_is_the_exact_d2_parts_contract() -> None:
assert "current_summary" not in ov and "summaries_done" not in ov, (
"the overview label carries no folder and no counts"
)
# "summaries": the exact template — optional folder, counts always.
i_fall = body.find("return fmtSyncLabel")
# "summaries": the exact template — optional folder, counts always,
# NO chunk part (the summaries phase's own (n/m) is its position —
# the status's chunk fields are cleared at this transition, so the
# branch never reads them).
i_fall = body.find("fmtSyncLabel(")
assert i_sum < i_fall, "the summaries branch precedes the fall-through"
sum_ = body[i_sum:i_fall]
assert 'status.current_summary ? ` ${status.current_summary}` : ""' in sum_, (
@@ -295,12 +341,40 @@ def test_fmt_sync_phase_label_is_the_exact_d2_parts_contract() -> None:
"count: `(${status.summaries_done}/${status.summaries_total})`"
) in sum_, "the count part is ALWAYS emitted in the summaries phase"
assert "total > 0" not in sum_, ("no conditional — the hook always carries a total")
assert 'chunk: ""' in ov, "the overview label carries no chunk part"
assert 'chunk: ""' in sum_, (
"the summaries label carries no chunk part (its (n/m) is its position)"
)
assert "chunks_total" not in ov, "the overview branch never reads the chunk fields"
assert "chunks_total" not in sum_, "the summaries branch never reads the chunk fields"
# anything else (null prelude, "import"): byte-identical to today's
# sync label — same builder, same fields, same order.
# sync label — same builder, same fields, same order — plus the
# phase-129 chunk part: "· chunk n/m" ONLY while chunks_total > 0
# (a file's embed batch is in flight — n the 1-based first chunk
# of the batch, m the file's total content chunks, batch
# granularity), "" otherwise (0/0 before the first batch — the
# part hides). The fall-through is the ONLY branch that reads the
# status's chunk fields.
i_destr = body.find("const { main, count } = fmtSyncLabel(")
assert -1 < i_destr < i_fall, "the fall-through destructures the phase-64 parts"
fall = body[i_destr:]
assert (
'return fmtSyncLabel("sync", status.current_file, '
"status.files_done, status.files_total);"
) in body[i_fall:]
'const { main, count } = fmtSyncLabel(\n'
' "sync", status.current_file, status.files_done, status.files_total\n'
" );"
) in fall, "the phase-64 parts builder, unchanged"
assert "status.chunks_total > 0" in fall, (
"the chunk part renders only while a batch is in flight"
)
assert "`· chunk ${status.current_chunk}/${status.chunks_total}`" in fall
i_guard = fall.find("status.chunks_total > 0")
i_else = fall.find(": \"\";", i_guard)
assert -1 < i_guard < i_else, "0/0 (before the first batch) → the empty chunk part"
assert "return { main, count, chunk }" in fall
# the bare builder (the upload job + the 202/409 click) returns NO
# chunk part — those labels stay bare.
bare = _fn(_js(), "fmtSyncLabel")
assert "chunk" not in bare, "fmtSyncLabel stays chunk-free (upload + the click stay bare)"
def test_enter_running_state_is_phase_aware_and_writes_the_parts() -> None:
@@ -312,8 +386,10 @@ def test_enter_running_state_is_phase_aware_and_writes_the_parts() -> None:
endpoint has no phase) and the 202/409 click keep the bare
fmtSyncLabel parts, byte-identical. Phase 128 (task 01): the entry
itself no longer writes the label — it hands the parts to the
SINGLE writer (setSyncLabel(main, count)), which lands the main
part, the pinned count part (hidden when absent), and the FULL
SINGLE writer (setSyncLabel(main, count, chunk) — phase 129:
the chunk part destructures with a "" default), which lands the
main part, the pinned count part (hidden when absent), the pinned
chunk part (phase 129 — hidden when absent), and the FULL
untruncated label on the button title + #sync-result (A4 — set
for EVERY running label; CSS ellipsizes the main part only)."""
body = _fn(_js(), "enterSyncRunningState")
@@ -332,8 +408,14 @@ def test_enter_running_state_is_phase_aware_and_writes_the_parts() -> None:
i_fall = body.find("fmtSyncLabel(kind, currentFile, done, total)")
assert 'kind === "sync" && status' in body
assert -1 < i_phase < i_fall, "the phase label precedes the fall-through"
# the parts are handed to the single writer (A4 lives in it).
i_set = body.find("setSyncLabel(main, count)")
# the parts are handed to the single writer (A4 lives in it) —
# phase 129: the chunk part destructures with a "" default (the
# bare builders — the upload job, the 202/409 click — return no
# chunk part, so the part stays hidden there).
assert "const { main, count, chunk = \"\" } =" in body, (
"the chunk part destructures with the '' default (bare builders return none)"
)
i_set = body.find("setSyncLabel(main, count, chunk)")
assert -1 < i_fall < i_set, "the parts are written after the split"
assert body.count("setSyncLabel(") == 1, "one writer call per entry"
assert "syncBtn.title =" not in body, ("the title write lives in setSyncLabel (A4)")
@@ -369,6 +451,211 @@ def test_settle_and_idle_labels_go_through_the_parts_writer() -> None:
assert "syncLabel.textContent" not in js, ("the parts writer is the single entry point")
# =====================================================================
# Phase 129 (task 04) — the label's third part: the chunk position
# =====================================================================
def test_sync_label_markup_ships_the_hidden_chunk_part() -> None:
"""#sync-label (the shell's RAG view) ships the phase-129 THIRD
part after the count span — the pinned "· chunk n/m" span
(.sync-label-chunk), hidden + empty by default (the same ship
contract as the count span: no chunk position at idle — the part
renders only while a file's embed batch is in flight)."""
html = _html()
btn = html[html.find('id="sync-btn"') : html.find("</button>", html.find('id="sync-btn"'))]
label = re.search(
r'<span\b[^>]*id="sync-label"[^>]*>'
r'<span class="sync-label-main">Sync sources</span>'
r'<span class="sync-label-count" hidden></span>'
r'<span class="sync-label-chunk" hidden></span>'
r"</span>",
btn,
)
assert label, (
"the #sync-label parts markup (main + hidden count + hidden chunk) is missing"
)
def test_sync_label_chunk_css_is_the_pinned_part() -> None:
""".sync-label-chunk (phase 129 task 04): the same pinned-part
language as .sync-label-count — flex 0 0 auto (NEVER clipped),
white-space nowrap, the --mono font (a position reads as data) —
and it sits right AFTER the count rule (both mono parts read as
data)."""
css = _css()
count_block = re.search(r"\.sync-label-count\s*\{", css)
chunk_block = re.search(r"\.sync-label-chunk\s*\{([^}]*)\}", css)
assert chunk_block, "styles.css must style .sync-label-chunk"
for prop in ("flex: 0 0 auto", "white-space: nowrap", "font-family: var(--mono)"):
assert prop in chunk_block.group(1), f".sync-label-chunk must carry {prop!r}"
assert count_block and count_block.start() < chunk_block.start(), (
"the chunk rule sits right after the count rule"
)
#: The node harness script (the test-document-viewer pattern): the
#: REAL extracted label functions run against stub element refs, and
#: every label state is snapshotted as a JSON row —
#: [tag, title, announcer, main, count, countHidden, chunk, chunkHidden].
#: No JS template literals in the script body (plain strings only —
#: the script is assembled by concatenation, no placeholders).
_CHUNK_NODE_HEAD = """
const syncLabelMain = { textContent: "" };
const syncLabelCount = { textContent: "", hidden: true };
const syncLabelChunk = { textContent: "", hidden: true };
const syncResult = { textContent: "" };
const syncBtn = {
disabled: false,
title: "",
attrs: {},
setAttribute(k, v) { this.attrs[k] = v; },
removeAttribute(k) { delete this.attrs[k]; },
classList: { add() {}, remove() {} },
};
const syncIcon = { classList: { add() {}, remove() {} } };
"""
_CHUNK_NODE_TAIL = """
const show = (tag) => {
console.log(JSON.stringify([
tag, syncBtn.title, syncResult.textContent,
syncLabelMain.textContent,
syncLabelCount.textContent, syncLabelCount.hidden,
syncLabelChunk.textContent, syncLabelChunk.hidden,
]));
};
// 1. import, batch in flight (the task's fixture values, 14/38):
enterSyncRunningState("sync", "src/a/b.md", 2, 3, {
phase: "import", current_file: "src/a/b.md", files_done: 2, files_total: 3,
current_chunk: 14, chunks_total: 38,
});
show("import-inflight");
// 2. import before the first batch (0/0) — the chunk part hides:
enterSyncRunningState("sync", "src/a/b.md", 2, 3, {
phase: "import", current_file: "src/a/b.md", files_done: 2, files_total: 3,
current_chunk: 0, chunks_total: 0,
});
show("import-prebatch");
// 3. the prelude (null phase, no file yet):
enterSyncRunningState("sync", null, 0, 0, {
phase: null, current_file: null, files_done: 0, files_total: 0,
current_chunk: 0, chunks_total: 0,
});
show("prelude");
// 4. the 202/409 click (no status yet):
enterSyncRunningState("sync", null, 0, 0);
show("click");
// 5. the upload run (no status — its endpoint has no chunk fields, D2):
enterSyncRunningState("upload", null, 0, 0);
show("upload");
// 6. overview (the chunk fields are cleared at this transition):
enterSyncRunningState("sync", null, 4, 4, {
phase: "overview", current_file: null, files_done: 4, files_total: 4,
current_chunk: 0, chunks_total: 0,
});
show("overview");
// 7. summaries (the phase's own (n/m) is its position):
enterSyncRunningState("sync", null, 4, 4, {
phase: "summaries", current_summary: "src/alpha", files_done: 4, files_total: 4,
summaries_done: 2, summaries_total: 3, current_chunk: 0, chunks_total: 0,
});
show("summaries");
// 8. the idle settle goes through the writer with no chunk text:
setSyncLabel("Sync sources");
show("idle");
"""
def _run_node(script: str) -> str:
proc = subprocess.run(["node", "-e", script], capture_output=True, text=True, timeout=60)
assert proc.returncode == 0, f"node failed: {proc.stderr}"
return proc.stdout
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_chunk_part_renders_only_in_the_import_phase_under_node() -> None:
"""Behavioral check (node) of the REAL label path — the extracted
fmtSyncLabel / fmtSyncPhaseLabel / setSyncLabel /
enterSyncRunningState run against stub element refs (the
test-document-viewer pattern). The pinned "· chunk n/m" part (the
task's fixture values):
* import, batch in flight (chunks_total 38 > 0): the chunk part
shows "· chunk 14/38" and the UNTRUNCATED title + #sync-result
read exactly "Syncing… src/a/b.md (2/3) · chunk 14/38" (A4);
* import before the first batch (0/0): the chunk part hides and
the label stays "Syncing… src/a/b.md (2/3)";
* every OTHER state — prelude (null phase), the 202/409 click (no
status), the upload run (no status, D2), overview, summaries (its
own (n/m) is its position), the idle settle — leaves the chunk
part hidden + empty.
"""
js = _js()
fns = "\n".join(
_fn(js, name)
for name in ("fmtSyncLabel", "fmtSyncPhaseLabel", "setSyncLabel", "enterSyncRunningState")
)
script = _CHUNK_NODE_HEAD + fns + _CHUNK_NODE_TAIL
rows = [json.loads(line) for line in _run_node(script).splitlines() if line]
by_tag = {row[0]: row for row in rows}
# row = [tag, title, announcer, main, count, countHidden, chunk, chunkHidden]
# 1. the in-flight import — the chunk part renders, the full
# untruncated label rides the title + the announcer (A4).
row = by_tag["import-inflight"]
assert row[3] == "Syncing… src/a/b.md", row
assert row[4] == "(2/3)" and row[5] is False, row
assert row[6] == "· chunk 14/38" and row[7] is False, (
"the chunk part shows the batch position (1-based first chunk / file total)"
)
assert row[1] == "Syncing… src/a/b.md (2/3) · chunk 14/38", row[1]
assert row[2] == row[1], "the announcer hears the whole thing (A4)"
# 2. the import before the first batch (0/0) — the part hides, the
# label is the byte-identical phase-64 string.
row = by_tag["import-prebatch"]
assert row[1] == "Syncing… src/a/b.md (2/3)", row[1]
assert row[2] == row[1]
assert row[6] == "" and row[7] is True, row
# 3. every other state — the chunk part is hidden + empty, and the
# title is the byte-identical pre-phase-129 label for that state.
for tag, expected_title in (
("prelude", "Syncing…"),
("click", "Syncing…"),
("upload", "Importing"),
("overview", "Writing KB overview…"),
("summaries", "Summarizing folders… src/alpha (2/3)"),
("idle", "Sync sources"),
):
row = by_tag[tag]
assert row[1] == expected_title, (tag, row)
assert row[2] == row[1], (tag, row)
assert row[6] == "" and row[7] is True, f"{tag} must hide the chunk part"
def test_every_other_state_passes_no_chunk_text() -> None:
"""Source-level pin of the render-only-while-chunks_total>0
contract: the import fall-through is the ONLY place in sources.js
that reads the status's chunk fields (the summaries/overview
branches and the bare builders never touch them), and every
NON-running label emission passes no chunk text (the part hides):
the settle/idle labels go through setSyncLabel(label) — the only
three-arg call site is the running entry."""
js = _js()
body = _fn(js, "fmtSyncPhaseLabel")
i_fall = body.find("fmtSyncLabel(")
pre = body[:i_fall]
assert "chunks_total" not in pre and "current_chunk" not in pre, (
"the overview/summaries branches never read the chunk fields"
)
fall = body[i_fall:]
assert "status.chunks_total" in fall and "status.current_chunk" in fall
assert js.count("setSyncLabel(main, count, chunk)") == 1
assert "setSyncLabel(label)" in js, "the settle/idle labels stay two-arg (no chunk)"
# ---------- the two-job tick (startSyncPolling) ----------