phase: 149_sync_progress_box
Build and Push Containers / build-and-push-app (push) Successful in 3m35s
Build and Push Containers / build-and-push-db (push) Successful in 11s

All verification complete. The `ink-soft`/`--surface` pair the box uses is already pinned AA by the existing theme E2E suites (8.2:1 dark, ~6.9:1 light). Everything is green — no fixes were needed.

**Phase 149 final verification report**

- Verified (no re-work needed): `#sync-progress` box wiring (single `setSyncLabel` writer with `running` flag; `is-running` class travels with the spin; aria-label owned by the writer), the panel/icon-only-button CSS, and the 5-test dedicated E2E; all 6 pre-existing sync E2Es' running pins migrated to the box, settled pins untouched.
- Dedicated E2E: `uv run pytest tests/e2e/test_sync_progress_box.py -v --no-cov` → **5 passed in 198.6 s** (isolation, slow-proxy mock; under the 600 s budget).
- Full suite: `uv run pytest --cov=app --cov-report=term-missing` → **3463 passed, app/ coverage 99%** (>90% gate).
- Migrated sync E2Es, each in isolation: `test_sync_button` 3 passed, `test_sync_cancel` 3, `test_sync_chunk_progress` 2, `test_sync_label_mobile` 4, `test_sync_summary_visibility` 3, `test_sync_upload_progress` 4 — all green.
- Lint/types: `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors.
- Criteria: in-flight spinner-only button + full untruncated wrapped box (desktop & mobile 390×844, `scrollWidth ≤ clientWidth`) ✓; settle byte-identical ("Synced HH:MM" + `3 added`, box hidden, aria restored) ✓; `#sync-result` sole live region (box `aria-hidden`), mid-run reload re-attaches ✓; WCAG (AA contrast both themes via the pinned ink-soft/surface pair, focus-visible, reduced-motion kill intact at styles.css L2201) ✓; cancel/409/error/upload/status API unchanged (green suites above) ✓.
- Documentation: README.md reviewed in full (sync-states paragraph now matches the shipped UI; env-var table matches `app/config.py` and covers all `.env.example` vars; pages/commands verified) and AGENTS.md reviewed in full — **verified consistent, no changes needed**.
- Notable: no defects found in this or prior work; no live infrastructure touched.
- Next pending phase: none — `todo/` will be empty once the harness moves 149 (only 149 was pending).
This commit is contained in:
2026-10-01 21:14:48 -04:00
parent b233c8d714
commit dd524246ec
33 changed files with 2512 additions and 393 deletions
+38 -5
View File
@@ -49,6 +49,17 @@ flight (the import phase after the first batch — hidden in every
other state); the markup pin below pins the exact three-part ship
shape.
Phase 149 (task 01): the running button becomes the SPINNER — the
writer gains the `running` flag (the running entry passes `true`,
the settle the default `false`), which reveals the detail box
(#sync-progress — the full untruncated label, presentational
aria-hidden; #sync-result stays the sole announcer) and owns the
button's accessible name (the full live label while running, the
ship value "Sync sources" restored on every settle); the
`is-running` class travels with the spin (added in the running
entry, removed in the settle). The pins below move onto that shape
(the aria-label write leaves the running entry for the writer).
Phase 129 (task 03): the status contract gains the in-flight file's
EMBED-chunk position — ``current_chunk`` (the 1-based first chunk of
the in-flight token-budgeted batch) + ``chunks_total`` (the file's
@@ -372,7 +383,12 @@ def test_sources_js_running_state_is_never_stale() -> None:
(phase 129 — hidden when absent), and the FULL untruncated label
on the button title + #sync-result (A4) — the direct span /
title / announcer writes are GONE from the entry (they live in
setSyncLabel)."""
setSyncLabel). Phase 149 (task 01): the writer call passes
`running = true` (the detail box reveals with the full label),
and the button's accessible name rides in the writer too (the
full live label — the icon-only running button keeps a name);
the entry no longer sets aria-label at all. The `is-running`
class is added here, with the spin."""
js = _text(SOURCES_JS)
body = _body(js, "enterSyncRunningState")
assert "syncBtn.disabled = true" in body
@@ -381,15 +397,22 @@ def test_sources_js_running_state_is_never_stale() -> None:
assert "fmtSyncPhaseLabel(status)" in body
assert "fmtSyncLabel(kind, currentFile, done, total)" in body
# …and the single writer lands the parts (A4 in it — the title is
# set for EVERY running label: the file-only rule is adjusted away).
assert "setSyncLabel(main, count, chunk)" in body
# set for EVERY running label: the file-only rule is adjusted
# away) with the phase-149 `running` flag (the box reveals).
assert "setSyncLabel(main, count, chunk, true)" in body
assert body.count("setSyncLabel(") == 1, "the entry writes ONLY through the parts writer"
assert "syncBtn.title =" not in body, ("the title write lives in setSyncLabel (A4)")
assert "syncLabel.textContent" not in body, ("no direct span write — the parts")
assert "syncResult.textContent" not in body, ("the announcer write lives in setSyncLabel (A4)")
assert 'syncBtn.setAttribute("aria-label", "Sync sources")' in body
assert 'syncBtn.setAttribute("aria-label"' not in body, (
"the aria-label write lives in setSyncLabel (phase 149: the full"
" live label while running)"
)
assert "syncBtn.classList.remove(\"is-error\")" in body
assert "syncIcon.classList.add(\"is-spinning\")" in body
assert "syncBtn.classList.add(\"is-running\")" in body, (
"the icon-only running button class travels with the spin"
)
def test_sources_js_terminal_states() -> None:
@@ -439,7 +462,11 @@ def test_sources_js_settles_the_button_on_terminal() -> None:
(failed included: retry-ready). Phase 128 (task 01): the settle /
idle label ("Sync sources", "Synced HH:MM") goes through the parts
writer — setSyncLabel(label), count hidden (absent), the full
string riding the title + #sync-result (A4, every label state)."""
string riding the title + #sync-result (A4, every label state).
Phase 149 (task 01): the `is-running` class is removed WITH the
spin (every terminal + both idles pass through the settle), and
the writer call's default `running = false` hides the detail box
+ restores the idle accessible name."""
js = _text(SOURCES_JS)
body = _body(js, "settleSyncButton")
assert "syncBtn.disabled = false" in body
@@ -447,7 +474,13 @@ def test_sources_js_settles_the_button_on_terminal() -> None:
assert 'syncBtn.removeAttribute("title")' in body
assert 'syncBtn.setAttribute("aria-label", "Sync sources")' in body
assert "syncIcon.classList.remove(\"is-spinning\")" in body
assert "syncBtn.classList.remove(\"is-running\")" in body, (
"the running class travels with the spin — the settle removes both"
)
assert "setSyncLabel(label)" in body, ("the settle label goes through the parts writer")
assert "setSyncLabel(label, true)" not in body, (
"the settle is NEVER a running state — the box hides (phase 149)"
)
assert "syncLabel.textContent" not in body, ("no direct span write — the parts")