phase: 129_sync_chunk_progress
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:
@@ -64,7 +64,13 @@ Test → observable mapping (Playwright Mapping Rule):
|
||||
``\\(\\d+/\\d+\\)`` once a file is in flight; ``.sync-label-main``
|
||||
carries ``Syncing… <file>`` — the FULL source/relative/path
|
||||
(visually ellipsized, but the DOM text and the button ``title``
|
||||
are untruncated, A4).
|
||||
are untruncated, A4). Phase 129: the label's THIRD pinned part —
|
||||
``.sync-label-chunk``, "· chunk n/m" (the in-flight file's
|
||||
embed-batch position, batch granularity) — renders while a file's
|
||||
embed batch is in flight (``chunks_total > 0``) and rides the
|
||||
untruncated ``title`` too; the snapshot accepts it (optional —
|
||||
the pre-first-batch frame has no chunk part yet; the phase-129
|
||||
dedicated suite pins it strictly).
|
||||
2. ``test_mobile_label_shows_summaries_count`` — same mobile
|
||||
viewport; through to the summaries phase: ``.sync-label-main``
|
||||
matches ``Summarizing folders…`` (the folder part optional — the
|
||||
@@ -74,12 +80,14 @@ Test → observable mapping (Playwright Mapping Rule):
|
||||
the running label (the phase-32 terminal ``Synced HH:MM``) and the
|
||||
count span is hidden + empty.
|
||||
3. ``test_desktop_label_unchanged`` — viewport 1280×800; the same
|
||||
sync: during import the full rendered label text (main + count
|
||||
concatenation) matches ``Syncing… <full source/relative/path>
|
||||
(n/m)`` — the pre-phase byte-identical string — and the label's
|
||||
width respects the ``min(16rem, 40vw)`` cap (bounding box width
|
||||
≤ 256 px + slack); the ``title`` carries the same untruncated
|
||||
string (A4).
|
||||
sync: during import the full rendered label text (main + count +
|
||||
optional phase-129 chunk concatenation) matches ``Syncing… <full
|
||||
source/relative/path> (n/m)`` (the pre-phase byte-identical
|
||||
string, the chunk part appended while a batch is in flight) and
|
||||
the label's width respects the ``min(16rem, 40vw)`` cap (bounding
|
||||
box width ≤ 256 px + slack — the shrinkable main part absorbs
|
||||
the extra pinned part); the ``title`` carries the same
|
||||
untruncated string (A4).
|
||||
4. ``test_pre_phase_css_reproduces_the_clip`` — the pin check: the
|
||||
SAME mobile run with the pre-phase-128 ``.sync-label`` rules
|
||||
re-injected (CSSOM ``insertRule`` into the origin's own linked
|
||||
@@ -179,8 +187,11 @@ FILE_RE = r"synclabel/(?:alpha|bravo)/(?:alpha|bravo)-[ab]\.md"
|
||||
IMPORT_MAIN_RE = re.compile(rf"^Syncing… {FILE_RE}$")
|
||||
IMPORT_COUNT_RE = re.compile(r"^\([1-4]/4\)$")
|
||||
#: The pre-phase byte-identical FULL string (main + " " + count — the
|
||||
#: single span's text before phase 128; the A4 title keeps it).
|
||||
IMPORT_FULL_RE = re.compile(rf"^Syncing… {FILE_RE} \([1-4]/4\)$")
|
||||
#: single span's text before phase 128; the A4 title keeps it), plus
|
||||
#: the OPTIONAL phase-129 chunk part (" · chunk n/m" — the in-flight
|
||||
#: file's embed-batch position, batch granularity) the title carries
|
||||
#: while a file's embed batch is in flight.
|
||||
IMPORT_FULL_RE = re.compile(rf"^Syncing… {FILE_RE} \([1-4]/4\)(?: · chunk \d+/\d+)?$")
|
||||
|
||||
#: The summaries label's PARTS (phase 98 task 02, unit-pinned): main
|
||||
#: "Summarizing folders… [source/folder]" (the folder part omitted
|
||||
@@ -236,7 +247,13 @@ def _md(title: str, body: str) -> str:
|
||||
#: <full source/relative/path>" (the untruncated A4 text — the visual
|
||||
#: ellipsis never touches textContent), the count part is VISIBLE
|
||||
#: (not ``hidden``) with the "(n/4)" position, and the button title
|
||||
#: carries the full untruncated label (main + " " + count, A4).
|
||||
#: carries the full untruncated label (main + " " + count, A4). Phase
|
||||
#: 129: the THIRD pinned part — .sync-label-chunk ("· chunk n/m", the
|
||||
#: in-flight file's embed-batch position) — rides the title too while
|
||||
#: a file's embed batch is in flight (chunks_total > 0): the title
|
||||
#: check accepts main + " " + count (+ " " + chunk when the part is
|
||||
#: rendered) — optional, since the pre-first-batch frame has no chunk
|
||||
#: part (the phase-129 dedicated suite pins the part strictly).
|
||||
_JS_IMPORT_SNAPSHOT = """
|
||||
() => {
|
||||
const label = document.querySelector('#sync-label');
|
||||
@@ -248,8 +265,12 @@ _JS_IMPORT_SNAPSHOT = """
|
||||
const reMain = /^Syncing… synclabel\\/(?:alpha|bravo)\\/(?:alpha|bravo)-[ab]\\.md$/;
|
||||
const reCount = /^\\([1-4]\\/4\\)$/;
|
||||
if (!reMain.test(main.textContent) || !reCount.test(count.textContent)) return null;
|
||||
const chunk = label.querySelector('.sync-label-chunk');
|
||||
const chunkText = (chunk && !chunk.hidden) ? chunk.textContent : '';
|
||||
const title = btn.getAttribute('title');
|
||||
if (title !== main.textContent + ' ' + count.textContent) return null;
|
||||
const full = main.textContent + ' ' + count.textContent
|
||||
+ (chunkText ? ' ' + chunkText : '');
|
||||
if (title !== full) return null;
|
||||
const rect = (el) => {
|
||||
const b = el.getBoundingClientRect();
|
||||
return { left: b.left, right: b.right, width: b.width, height: b.height };
|
||||
@@ -257,6 +278,7 @@ _JS_IMPORT_SNAPSHOT = """
|
||||
return {
|
||||
mainText: main.textContent,
|
||||
countText: count.textContent,
|
||||
chunkText,
|
||||
title,
|
||||
labelRect: rect(label),
|
||||
countRect: rect(count),
|
||||
@@ -658,7 +680,14 @@ def test_mobile_label_fits_viewport_with_count_in_import(
|
||||
# A4: the button title carries the UNTRUNCATED label — the full
|
||||
# path, not the ellipsized visual tail (the in-browser check
|
||||
# already required title == main + " " + count; pin it here too).
|
||||
assert snap["title"] == snap["mainText"] + " " + snap["countText"], snap
|
||||
# Phase 129: the pinned chunk part ("· chunk n/m") rides the title
|
||||
# while a file's embed batch is in flight (optional — the
|
||||
# pre-first-batch frame has none; the phase-129 dedicated suite
|
||||
# pins it strictly).
|
||||
full = snap["mainText"] + " " + snap["countText"]
|
||||
if snap["chunkText"]:
|
||||
full += " " + snap["chunkText"]
|
||||
assert snap["title"] == full, snap
|
||||
# The geometry contract (the fix): label inside the viewport, the
|
||||
# count visible and never clipped.
|
||||
_assert_count_not_clipped(snap, MOBILE_VIEWPORT["width"])
|
||||
@@ -727,9 +756,12 @@ def test_desktop_label_unchanged(
|
||||
_JS_IMPORT_SNAPSHOT, timeout=SETTLE_TIMEOUT_MS
|
||||
).json_value()
|
||||
|
||||
# The pre-phase byte-identical FULL string (main + " " + count):
|
||||
# "Syncing… <full source/relative/path> (n/m)".
|
||||
# The pre-phase byte-identical FULL string (main + " " + count +
|
||||
# the phase-129 chunk part while a batch is in flight):
|
||||
# "Syncing… <full source/relative/path> (n/m)" (+ " · chunk n/m").
|
||||
full = snap["mainText"] + " " + snap["countText"]
|
||||
if snap["chunkText"]:
|
||||
full += " " + snap["chunkText"]
|
||||
assert IMPORT_FULL_RE.match(full), full
|
||||
assert snap["title"] == full, snap
|
||||
# The count part is visible and pinned inside the label (no
|
||||
|
||||
Reference in New Issue
Block a user