# Phase 193 — The sync label counts DOWN: "N remaining" **Source:** `TODO.md` L6 — "The sync status should show how many files are left to sync, not how many files out of the total." **Story:** n/a (owner directive) **Depends on:** none functionally (builds on the completed phase-64/98/128/129/152 sync-label lineage). ## Evidence (diagnosed from the code, read this not the chat) 1. **The running label counts UP.** `frontend/assets/sources.js` `fmtSyncLabel` (L1053–1058): the import phase and the upload job render `count = total > 0 ? `(${done}/${total})` : ""` — `Syncing… notes/a.md (1234/27000)`. The owner wants the distance to the end, not the position in the whole: `25766 remaining`. 2. **The data already exists.** `SyncStatus.files_done` / `files_total` (the phase-64 payload — the running label's only inputs) carry everything: `N = total − done` is a subtraction, no server change, no new field, no new poll. The `#sync-progress` line (the phase-152 A4: the untruncated full label rides the button title + `#sync-progress` via `setSyncLabel`) gets the new part through the SAME builder — nothing to change there. 3. **The other count parts are different animals — they stay.** `· chunk n/m` (phase 129 — one file's embed batch), `· image n/m` (phase 152 — one PDF's vision span), and the summaries phase's `(n/m)` (phase 98 — the FOLDER position, always emitted, `fmtSyncPhaseLabel` L1089+) are per-file / per-folder position parts, not "files of the total". The terminal result line (`counts_line` — "N added · M updated …") is a different surface and is untouched. ## Design (shared by all tasks — the executor reads this, not the chat) - **D1 — the part (task 01).** `fmtSyncLabel`'s `count` becomes `total > 0 && total - done > 0 ? `${total - done} remaining` : ""` — the owner-confirmed shape (ASSUMPTION A1, confirmed 2026-10-18): - `N remaining` shows while `N ≥ 1`; at the final file (`N == 0`) the part is HIDDEN — the main part names the file in flight, and a "0 remaining" next to the last file's name is the stale-looking edge the owner's complaint is about; - plain integer (no thousands separator — the existing count part is plain; the house label style); - the POSITION is unchanged (main → count → chunk → image — the phase-128 parts rework order); - the UPLOAD job shares the builder and the format (owner-confirmed A2 — `Importing N remaining`; the upload's `files_total` is the pre-walk count, the phase-98 contract — the same subtraction semantics); - `total === 0` (the pre-walk null total, the phase-64 contract) → no count part (unchanged); - the chunk / image parts and the summaries-phase folder position are UNTOUCHED (byte-identical); - the docstring (L1044–1058 region) is updated to the new part (the phase-64/98 lineage paragraph keeps its history, the `(done/total)` description becomes the `N remaining` one). - **D2 — the pinned suites move (task 02).** The running-label count pins retarget to the new part; the terminal-line pins stay unedited. The definitive list is found by `grep -rn "Syncing…\|Importing" tests/e2e/*.py | grep -E "\\\\d|/\{N|[0-9]+\)"` at execution time (the known pins: `test_sync_label_mobile.py` `IMPORT_FULL_RE` L226 + the docstring L93/L213; `test_sync_upload_progress.py` L653/L667/L850). ## ASSUMPTIONS (owner-confirmed 2026-10-18) - **A1:** `N remaining` while `N ≥ 1`, hidden at `N == 0` (the final file — the main part names it). - **A2:** the upload job shares the format (one builder, one shape). ## Context - `frontend/assets/sources.js` — `fmtSyncLabel` (L1053–1058), `fmtSyncPhaseLabel` (L1089+, the phase-aware wrapper: overview / summaries / import), `setSyncLabel` (the A4 full-label placement — the button title + `#sync-progress`), the `counts_line` helper (the terminal result line — UNTOUCHED); `app/schemas.py` `SyncStatus` (`files_done` / `files_total` — the payload, UNTOUCHED); `app/api/sync.py` (the status endpoint — UNTOUCHED). - `tests/e2e/test_sync_label_mobile.py`, `tests/e2e/test_sync_upload_progress.py`, `tests/e2e/test_sync_chunk_progress.py`, `tests/e2e/test_sync_summary_visibility.py` (the phase-64/98/128/129/152 label suites — the count-part pins; the chunk/image/summaries pins stay), `tests/e2e/test_sync_git_freeze.py` + `test_sync_session_reset.py` + `test_fast_path_deferred_content.py` (the `counts_line` terminal pins — unedited). ## Tasks 1. `01_remaining_label.md` — the `fmtSyncLabel` count part becomes `N remaining` (the D1 spec), the docstring update, the JS marker pin. 2. `02_update_pinned_e2e.md` — the running-label count pins retarget to the new part (the D2 list); the full suite green. 3. `03_e2e_and_gates.md` — the phase's own suite (the count-down through the real UI), the full gates, the docs pass, the atomic commit. ## Testing & Quality - **Unit (JS marker pin):** the new count expression (`total - done > 0` guard, the plain-integer shape, the `total > 0` guard kept, the upload kind sharing the builder — the marker names the expression). - **E2E (regression, task 02):** the retargeted count pins + the chunk / image / summaries pins green; the terminal `counts_line` pins green UNEDITED. - **E2E (phase, AGENTS rule 4, task 03):** `tests/e2e/test_sync_remaining_count.py`, run in isolation — the count-down arm (the part decreases across polls; hidden at the final file; the chunk part coexists), the upload arm (the shared format). - **Coverage:** **>90%** on `app/` (unchanged — the task is frontend; the app-side gate stays green). - **Lint/types:** `uv run ruff check . && uv run pyright`. ## Documentation & Consistency - Final pass reviews `README.md` and `AGENTS.md` in their entirety against the post-phase app and fixes every inconsistency found, pre-existing ones included. - `README.md`: the sync section's running-label description (the `(done/total)` example, if present) becomes the `N remaining` one; `AGENTS.md` — the phase-64 sync-label clause (if it names the count part) gains the new shape (task 03 — verify, do not add if absent). ## Completion Criteria - [ ] the running file-count part is `N remaining` (`N = total − done`, plain, shown while `N ≥ 1`, hidden at `N == 0`, `total === 0` → no part) — the marker pin + the E2E count-down arm. - [ ] the upload label uses the same shape (the E2E upload arm); the chunk / image / summaries parts are byte-identical (the unedited pins green). - [ ] the terminal result line is byte-identical (the `counts_line` pins green unedited); the `#sync-progress` line carries the new part through the same builder (the A4 pin). - [ ] test suite green, coverage >90%; `uv run ruff check . && uv run pyright` clean. - [ ] no behavior change in completed phases (the server, the payload, the phases of the run all untouched — the change is the one label part). - [ ] one atomic Conventional Commits commit with `--no-gpg-sign`.