phase: 193_sync_remaining_count
**Phase 193 — final verification pass: all gates green.**
- **Verified (all 3 tasks already complete in `complete/193_sync_remaining_count/`):** `fmtSyncLabel`'s count part is `total > 0 && total - done > 0 ? \`${total - done} remaining\` : ""` (plain int, hidden at `N==0`, `total===0` → no part); docstrings updated; chunk/image/summaries parts + `counts_line` terminal + `app/` byte-untouched (`git diff --stat`: no `app/` files).
- **Completion criteria:** `N remaining` shape + marker pins ✅ · upload shares the builder (E2E upload arm) ✅ · chunk/image/summaries pins green ✅ · terminal `counts_line` green unedited + A4 title/`#sync-progress` pin ✅ · suite green, coverage 99% ✅ · lint/types clean ✅ · commit + `todo/→complete/` left to the harness (no git index/history commands run).
- `uv run pytest -q --cov=app --cov-report=term-missing` → **exit 0**, junit: **4792 tests, 0 failures/errors/skipped**, `TOTAL 7181 16 → 99%` (>90%)
- `uv run pytest tests/e2e/test_sync_remaining_count.py -v --no-cov` → **3 passed** (isolated, 53 s)
- Isolated `--no-cov` sweep of the 14 sync-lineage suites (label_mobile, upload_progress, chunk_progress, image_progress, progress_box, summary_visibility, archive_upload_sources, status_freshness, upload_no_scan, peak_memory_visible, session_reset, fast_path_deferred_content, git_freeze, smoke) → **14/14 exit 0**
- `uv run ruff check .` → All checks passed · `uv run pyright` → **0 errors**
- **Docs:** README.md reviewed in full (all 2408 lines) + AGENTS.md. Fixed 2 inconsistencies: the sync running-label clause claimed the upload label shows the count — now states it shares the ONE builder/shape but reports no counts since phase 90 (renders bare); phase-190's "the other ~72 rows" → "the other 83 rows of the panel's 86-row catalogue" (mechanically: 86 CATALOG rows, 84 overridable). `N remaining` already in the sync paragraph; task-01's `(n/m)` examples gone. AGENTS.md verified consistent (no clause names the running count part → unchanged per the phase's "do not add if absent"). Mechanically re-verified: all 86 `BOR_*` settings documented + defaults match, `.env.example` 88 vars covered, 0 broken README anchors, all cited file paths exist, 17 palette colors, migrations head `0030`, pages/shell routes, ports 8123/8901, E2E port allocations (new suite's 8211/8914 unique).
- **Notable:** task 02's documented deviation stands — `files_done` is the importer's 1-based *current* file, so the phase doc's `[1-4]` range is unreachable; count-bearing frames are `1 … N−1` (pinned). Pre-existing, untouched: `E2E_APP_PORT_TOOLBUDGET`/`SAVEDOC` both 8129 and `SYNCUP`/`SYCNCEL` both 8133 — harmless under the one-suite-at-a-time rule.
- **Next pending phase:** none — `193_sync_remaining_count` is the last entry in `.agents/phases/todo/`.
This commit is contained in:
@@ -4,7 +4,7 @@ the **button is the loading spinner** (``is-running`` hides the label
|
||||
parts; the spinning icon is the whole control) and the bordered text
|
||||
box directly under the button row (``#sync-progress``) carries the
|
||||
**EXACT full untruncated live label** — ``Syncing…
|
||||
<source/relative/path> (n/m) · chunk n/m`` — wrapping, never clipping
|
||||
<source/relative/path> N remaining · chunk n/m`` — wrapping, never clipping
|
||||
(the long ``source/relative/path`` that overflows the pill is the
|
||||
fixture's whole reason to exist). Phase 150 folded the announcer into
|
||||
the box: it is the **single sync text surface AND the sole aria-live
|
||||
@@ -22,6 +22,12 @@ behavioral proof through the REAL sync path: a real ``git clone`` of a
|
||||
local ``file://`` fixture repo, a real import, the slowed mock LLM so
|
||||
the in-flight state is observable.
|
||||
|
||||
Phase 193 (task 02) re-points the file-count part of that label to the
|
||||
distance to the end — ``N remaining`` with ``N = total - done`` — so the
|
||||
long file (the FIRST of the three) renders ``2 remaining`` while the
|
||||
endpoint's ``files_done``/``files_total`` stay the 1-based 1/3 position.
|
||||
The ``· chunk`` part and the terminal counts line are byte-identical.
|
||||
|
||||
Run in isolation (DB must be up: ``podman compose up -d db``; git on PATH —
|
||||
a documented environment prerequisite, phase 28):
|
||||
|
||||
@@ -105,7 +111,7 @@ in one JS task):
|
||||
phase-134 contract unchanged).
|
||||
3. ``test_box_carries_the_full_untruncated_label`` — during the
|
||||
in-flight window of the LONG-path file (poll until the box text is
|
||||
``Syncing… <full long path> (1/3)`` — the path string IN FULL, the
|
||||
``Syncing… <full long path> 2 remaining`` — the path string IN FULL, the
|
||||
pre-phase ellipsis would have truncated it): the box's
|
||||
``scrollWidth <= clientWidth`` AND the text's ``scrollWidth <=
|
||||
clientWidth`` (the wrap engaged — nothing is clipped), the box IS the
|
||||
@@ -114,7 +120,7 @@ in one JS task):
|
||||
box's text too, and the box ink on the panel background clears WCAG
|
||||
2.1 AA (≥ 4.5:1). Then a retry loop over the slow embed window pins
|
||||
the phase-129 chunk part IN THE BOX (``Syncing… <full long path>
|
||||
(1/3) · chunk n/m`` — the part is only present while an embed batch
|
||||
2 remaining · chunk n/m`` — the part is only present while an embed batch
|
||||
is in flight; the proxy's 1.5 s delay makes the ~15 s window ≫ the
|
||||
2 s poll).
|
||||
4. ``test_mobile_box_full_width`` — a fresh page at the house mobile
|
||||
@@ -226,7 +232,8 @@ N_FILES = 3
|
||||
#: The in-flight label's file part — the status endpoint's
|
||||
#: ``current_file`` = ``<source>/<rel>`` (the sync's progress hook),
|
||||
#: verbatim. The long file is the FIRST of the three (the sorted walk —
|
||||
#: ``notes/a-…`` < ``notes/short-…``), so its count part is ``(1/3)``.
|
||||
#: ``notes/a-…`` < ``notes/short-…``), so its phase-193 count part is
|
||||
#: ``2 remaining`` (N = total - done = 3 - 1).
|
||||
LONG_PATH = f"{SOURCE}/{LONG_REL}"
|
||||
|
||||
#: The long file's shape (module docstring): an H1 preamble (one chunk)
|
||||
@@ -370,7 +377,7 @@ _JS_RUNNING_SNAPSHOT = """
|
||||
|
||||
#: Test 3, wait A — the in-flight window of the LONG-path file: the box
|
||||
#: is VISIBLE and its text is the FULL label for the long file —
|
||||
#: "Syncing… <full long path> (1/3)" (+ the optional phase-129
|
||||
#: "Syncing… <full long path> 2 remaining" (+ the optional phase-129
|
||||
#: " · chunk n/m" part while an embed batch is in flight — the
|
||||
#: pre-first-batch frame has none) — the complete
|
||||
# source/relative/path IN the text (the pre-phase ellipsis would have
|
||||
@@ -385,10 +392,10 @@ _JS_LONG_FILE_SNAPSHOT = """
|
||||
const btn = document.querySelector('#sync-btn');
|
||||
if (!box || !textEl || !btn || box.hidden) return null;
|
||||
const t = textEl.textContent;
|
||||
const prefix = 'Syncing… ' + longPath + ' (';
|
||||
const prefix = 'Syncing… ' + longPath + ' ';
|
||||
if (!t.startsWith(prefix)) return null;
|
||||
const tail = t.slice(prefix.length);
|
||||
if (!/^\\d+\\/3\\)( · chunk \\d+\\/\\d+)?$/.test(tail)) return null;
|
||||
if (!/^2 remaining( · chunk \\d+\\/\\d+)?$/.test(tail)) return null;
|
||||
const cs = (el) => getComputedStyle(el);
|
||||
return {
|
||||
boxText: t,
|
||||
@@ -407,8 +414,8 @@ _JS_LONG_FILE_SNAPSHOT = """
|
||||
"""
|
||||
|
||||
#: Test 3, wait B — the phase-129 chunk part IN THE BOX during the slow
|
||||
#: embed window: the box text is "Syncing… <full long path> (1/3) ·
|
||||
#: chunk n/m" (the part is only present while an in-flight file's embed
|
||||
#: embed window: the box text is "Syncing… <full long path>
|
||||
#: 2 remaining · chunk n/m" (the part is only present while an in-flight file's embed
|
||||
#: batch is in flight — the proxy's 1.5 s delay makes the ~15 s
|
||||
#: long-file window ≫ the 2 s poll, so the retry loop resolves
|
||||
#: deterministically).
|
||||
@@ -418,7 +425,7 @@ _JS_LONG_FILE_CHUNK_SNAPSHOT = """
|
||||
const textEl = document.querySelector('#sync-progress-text');
|
||||
if (!box || !textEl || box.hidden) return null;
|
||||
const t = textEl.textContent;
|
||||
if (!t.startsWith('Syncing… ' + longPath + ' (1/3) · chunk ')) return null;
|
||||
if (!t.startsWith('Syncing… ' + longPath + ' 2 remaining · chunk ')) return null;
|
||||
const m = t.match(/· chunk (\\d+)\\/(\\d+)$/);
|
||||
if (!m) return null;
|
||||
return {
|
||||
@@ -453,7 +460,7 @@ _JS_MOBILE_SNAPSHOT = """
|
||||
if (!box || !textEl || !btn || !label || !icon || !container || !head) return null;
|
||||
if (box.hidden) return null;
|
||||
const t = textEl.textContent;
|
||||
if (!t.startsWith('Syncing… ' + longPath + ' (')) return null;
|
||||
if (!t.startsWith('Syncing… ' + longPath + ' 2 remaining')) return null;
|
||||
const cs = (el) => getComputedStyle(el);
|
||||
const rect = (el) => {
|
||||
const b = el.getBoundingClientRect();
|
||||
@@ -998,7 +1005,7 @@ def test_box_carries_the_full_untruncated_label(
|
||||
page: Page, app_url: str, db_ready: None
|
||||
) -> None:
|
||||
"""During the in-flight window of the LONG-path file (poll until the
|
||||
box text is ``Syncing… <full long path> (1/3)`` — the slow proxy
|
||||
box text is ``Syncing… <full long path> 2 remaining`` — the slow proxy
|
||||
makes the ~15 s span deterministic): the box's text contains the
|
||||
FULL label shape with the complete ``source/relative/path`` as a
|
||||
substring (the pre-phase ellipsis would have truncated it — the
|
||||
@@ -1022,7 +1029,7 @@ def test_box_carries_the_full_untruncated_label(
|
||||
|
||||
# The FULL label shape — the complete long path IN the text (the
|
||||
# clipping trigger, untruncated).
|
||||
expected_prefix = f"Syncing… {LONG_PATH} (1/3)"
|
||||
expected_prefix = f"Syncing… {LONG_PATH} 2 remaining"
|
||||
assert snap["boxText"].startswith(expected_prefix), snap["boxText"]
|
||||
assert LONG_PATH in snap["boxText"], snap["boxText"]
|
||||
# The wrap engaged — no horizontal overflow (the whole point of the
|
||||
@@ -1084,7 +1091,9 @@ def test_mobile_box_full_width(page: Page, app_url: str, db_ready: None) -> None
|
||||
|
||||
# The label wraps — the full long path is IN the box's text (the
|
||||
# old mobile-stuffing ellipsis is gone).
|
||||
assert snap["boxText"].startswith(f"Syncing… {LONG_PATH} ("), snap["boxText"]
|
||||
assert snap["boxText"].startswith(
|
||||
f"Syncing… {LONG_PATH} 2 remaining"
|
||||
), snap["boxText"]
|
||||
assert LONG_PATH in snap["boxText"], snap["boxText"]
|
||||
# The wrap engaged — no horizontal clipping at the phone width.
|
||||
assert snap["boxScrollWidth"] <= snap["boxClientWidth"], snap
|
||||
|
||||
Reference in New Issue
Block a user