phase: 193_sync_remaining_count
Build and Push Containers / build-and-push-app (push) Successful in 2m3s
Build and Push Containers / build-and-push-db (push) Successful in 16s

**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:
2026-10-19 17:30:03 -04:00
parent fb17d15fbe
commit ae080ac7f3
36 changed files with 2111 additions and 174 deletions
+3 -3
View File
@@ -73,7 +73,7 @@
* disabled, title cleared) plus startUploadPolling(): a 2 s poll
* of GET /api/git-sources/upload/status that renders the bare
* "Processing…" label for the WHOLE run (phase 90, A2 — the run
* is unpack + register only: no file, no "(n/m)" counts, no
* is unpack + register only: no file, no "N remaining" count, no
* title) and settles it: success → the ready-for-sync line
* ("Uploaded <name> (<stamp>) — press Sync sources to import
* it." — the phase-154 completion stamp, fmtUploadStamp off the
@@ -1651,7 +1651,7 @@ export async function mount(root) {
* title cleared) + startUploadPolling() own it — a 2 s poll of
* GET /api/git-sources/upload/status that renders the bare
* "Processing…" label for the WHOLE run (phase 90, A2 — the unpack
* has no file-level progress: no file, no "(n/m)" counts, no title)
* has no file-level progress: no file, no "N remaining" count, no title)
* and settles it: success → the ready-for-sync line ("Uploaded
* <name> (<stamp>) — press Sync sources to import it." — the
* phase-154 completion stamp, fmtUploadStamp off the status's
@@ -1786,7 +1786,7 @@ export async function mount(root) {
* poll OWNS the button). Each tick fetches
* GET /api/git-sources/upload/status: running → the bare
* "Processing…" label for the whole run (phase 90, A2 — the unpack
* has no file-level progress: no file, no "(n/m)" counts, the title
* has no file-level progress: no file, no "N remaining" count, the title
* stays clear) + reschedule; success → stop + the ready-for-sync
* result line + the announcement + the row reload (NO toast — it
* fired at the 202, A2); failed → stop + the sanitized server error
+33 -14
View File
@@ -160,7 +160,8 @@
* <source/folder> (n/m)" (the folder part omitted while
* current_summary is null — the phase's first poll), anything else
* (the null prelude, "import") → today's "Syncing… <file> (n/m)"
* byte-identical. The UPLOAD job (its status has no phase) keeps the
* byte-identical (the count part's TEXT is `N remaining` — the distance
* to the end — since phase 193). The UPLOAD job (its status has no phase) keeps the
* bare label. The untruncated label rides the button title (now set
* for EVERY running label, not just file labels) + #sync-progress as
* before (A4).
@@ -309,7 +310,8 @@
* Phase 128 (task 01) — the sync label becomes PARTS: the running
* label's string is split into a shrinks-to-fit MAIN part (prefix +
* the file/folder path — the part CSS may ellipsize) and a pinned
* COUNT part ("(n/m)" — the position, NEVER clipped), so on a phone
* COUNT part ("(n/m)" — the position, NEVER clipped — the text is
* `N remaining` since phase 193), so on a phone
* the file path ellipsizes to fit while the count stays on-screen
* (the phase-64 bug: with max-width lifted, the inline-block sized to
* its full content and the count ran off-screen). #sync-label now
@@ -332,7 +334,8 @@
* the initSyncButton re-attach — goes through setSyncLabel(main)
* (count hidden). The full-string shapes are byte-identical to the
* pre-task labels ("Syncing…", "Syncing… <file>",
* "Syncing… <file> (n/m)", "Importing", "Writing KB overview…",
* "Syncing… <file> (n/m)" — `N remaining` since phase 193, "Importing",
* "Writing KB overview…",
* "Summarizing folders… <folder> (n/m)"); textContent only — no HTML
* strings (the house rule).
*
@@ -747,7 +750,7 @@ export async function mount(root) {
* tree, in order (startSyncPolling):
* 1. sync running → the phase-aware label (Phase 98 task 02, D2)
* — the null-phase prelude + "import" →
* "Syncing… <file> (n/m)" — bare "Syncing…"
* "Syncing… <file> N remaining" — bare "Syncing…"
* until the import's first file (clone/pull,
* A4, byte-identical); "overview" → "Writing KB
* overview…"; "summaries" → "Summarizing
@@ -813,7 +816,8 @@ export async function mount(root) {
* Elements: #sync-btn (the button), #sync-label (the text —
* Phase 128 (task 01): the PARTS — .sync-label-main, the
* shrinks-to-fit prefix + file/folder, + .sync-label-count, the
* pinned "(n/m)" position (hidden when absent); phase 129 (task
* pinned count — the phase-193 "N remaining" (hidden when absent);
* phase 129 (task
* 04): + .sync-label-chunk, the pinned "· chunk n/m" embed-batch
* position of the in-flight file (hidden in every state but the
* import phase after the file's first batch); phase 152 (task 03):
@@ -917,7 +921,7 @@ export async function mount(root) {
* mainText lands in the MAIN part (.sync-label-main — the
* shrinks-to-fit prefix + file/folder, the part CSS ellipsizes);
* countText lands in the COUNT part (.sync-label-count — the pinned
* "(n/m)" position, NEVER clipped); chunkText (phase 129) lands in
* phase-193 "N remaining", NEVER clipped); chunkText (phase 129) lands in
* the CHUNK part (.sync-label-chunk — the pinned "· chunk n/m"
* embed-batch position of the in-flight file); imageText (phase
* 152) is the in-flight PDF's "· image n/m" vision-image position —
@@ -1034,9 +1038,20 @@ export async function mount(root) {
* BARE prefix shows during the clone/pull (sync), before any file
* is indexed. Phase 90: the upload run is unpack + register only
* (no scan), so its status never carries a file or counts — the
* "Importing" label is always the bare one. The counts — the PINNED
* count part, never clipped (Phase 128) — appear only once the
* import has started (total > 0). The builders return the PARTS
* "Importing" label is always the bare one — and when an upload
* status DOES carry counts the upload job shares this builder and
* the shape (phase 193, A2: the upload's files_total is the
* pre-walk count, the phase-98 contract — the same subtraction
* semantics). The count — the PINNED count part, never clipped
* (Phase 128) — is the phase-193 COUNT-DOWN part (the owner's
* "how many files are left, not how many files out of the
* total"): `N remaining` with N = total - done, shown while
* N >= 1 and HIDDEN at the final file (N == 0 — the main part
* names it; a "0 remaining" next to the last file's name is the
* stale-looking edge); plain integer, no thousands separator. The
* total > 0 guard stays (the phase-64 pre-walk contract — no count
* part before the walk knows the total). The builders return the
* PARTS
* ({ main, count }); setSyncLabel writes them (the untruncated full
* string — main + " " + count — rides the button title +
* #sync-progress, A4). Phase 98 (task 02): the SYNC job's
@@ -1053,7 +1068,7 @@ export async function mount(root) {
function fmtSyncLabel(kind, currentFile, done, total) {
const prefix = kind === "upload" ? "Importing" : "Syncing…";
const main = currentFile ? `${prefix} ${currentFile}` : prefix;
const count = total > 0 ? `(${done}/${total})` : "";
const count = total > 0 && total - done > 0 ? `${total - done} remaining` : "";
return { main, count };
}
@@ -1069,8 +1084,11 @@ export async function mount(root) {
* empty (the summaries phase's own (n/m) is its position — the
* status's chunk + image fields are cleared at this transition);
* anything else (the null prelude, "import") → the phase-64
* fmtSyncLabel parts, byte-identical (same builder, same fields,
* same order) + the phase-129 CHUNK part: "· chunk n/m" ONLY while
* fmtSyncLabel parts (same builder, same fields, same order —
* since phase 193 its count part reads `N remaining`, N =
* total - done, hidden at the final file; the summaries (n/m)
* folder position and the chunk/image parts below stay as they
* are) + 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), empty before the first batch (0/0) so
@@ -1099,8 +1117,9 @@ export async function mount(root) {
image: "", // the image position belongs to the import phase only
};
}
// null phase (the prelude) or "import" — today's byte-identical
// sync label (the phase-64 contract, unchanged) + the phase-129
// null phase (the prelude) or "import" — the phase-64 label
// shape (since phase 193 its count part reads `N remaining`) +
// the phase-129
// chunk part (hidden before the in-flight file's first batch) +
// the phase-152 image part (hidden before the in-flight PDF's
// first vision describe call).
+6 -4
View File
@@ -2898,14 +2898,16 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
@media (prefers-reduced-motion: reduce) {
.sync-btn .sync-icon.is-spinning { animation: none; }
}
/* Phase 64: the live-file label ("Syncing… <file> (n/m)" / "Importing
<file> (n/m)") — a long source/relative/path ellipsizes inside the
/* Phase 64: the live-file label ("Syncing… <file> N remaining" /
"Importing <file> N remaining" — phase 193: the file count is the
distance to the end, `total - done`, hidden on the final file) — a
long source/relative/path ellipsizes inside the
pill; the full path lives in the button title + the #sync-progress
box (the aria-live announcer — phase 150: the SOLE sync text
surface). Phase 128 (task 02): the label is a FLEX ROW
of parts — .sync-label-main (the prefix + file/folder: min-width
0, the shrinkable item whose ellipsis engages) + .sync-label-count
(the pinned "(n/m)": flex 0 0 auto, NEVER clipped — the
(the pinned "N remaining": flex 0 0 auto, NEVER clipped — the
.sync-progress-text mono language, a position reads as data). Phase 129
(task 04): a THIRD pinned part — .sync-label-chunk (the "· chunk
n/m" embed-batch position of the in-flight file, the same pinned
@@ -2925,7 +2927,7 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
white-space: nowrap;
}
.sync-label-count {
flex: 0 0 auto; /* NEVER clipped — the (n/m) position is the point */
flex: 0 0 auto; /* NEVER clipped — the N remaining count is the point */
white-space: nowrap;
font-family: var(--mono); /* the .sync-progress-text mono language (position reads as data) */
}