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
+231 -38
View File
@@ -92,6 +92,23 @@ contract: shown + enabled in EVERY sync-running state (the entry
shows it for kind "sync"), hidden + disabled for the upload run
(the upload job is NOT cancellable in this phase) and after the
settle (settleSyncButton always hides it).
Phase 149 (task 01) adds the `running` flag to the single writer:
setSyncLabel(mainText, countText = "", chunkText = "", running =
false) also writes the SAME untruncated full label into the detail
box (#sync-progress's .sync-progress-text — textContent only) and
reveals the box IFF running (the running entry passes true, the
settle passes the default false), and owns the button's accessible
name (full live label while running, the ship value "Sync sources"
restored on every non-running call). The running entry passes
`running = true` + adds the `is-running` class (with the spin), and
no longer sets aria-label (the writer owns it); the settle removes
`is-running` (with the spin). The node harness rows gain the box's
text/hidden pair, the button's `is-running` + the icon's
`is-spinning` flags, and the button's aria-label — the behavioral
contract: box visible + full label + is-running + spinning + the
aria-label equal to the full label in EVERY running state, box
hidden + the idle flags + the restored aria-label after the settle.
"""
from __future__ import annotations
@@ -256,22 +273,33 @@ def test_fmt_sync_label_returns_the_parts_contract() -> None:
def test_set_sync_label_is_the_single_parts_writer() -> None:
"""setSyncLabel(mainText, countText = "", chunkText = "") — Phase
128 (task 01) + Phase 129 (task 04): the SINGLE point that writes
#sync-label. The MAIN part gets mainText (textContent only — the
house rule, no HTML strings); the COUNT part gets countText when
present (textContent + hidden=false — the "(n/m)" position is
visible, NEVER clipped) and is emptied + hidden=true when absent;
the CHUNK part (phase 129) gets chunkText under the SAME contract
(present → the pinned "· chunk n/m" is visible, absent → emptied
+ hidden=true — the part renders only while a file's embed batch
"""setSyncLabel(mainText, countText = "", chunkText = "",
running = false) — Phase 128 (task 01) + Phase 129 (task 04):
the SINGLE point that writes #sync-label. The MAIN part gets
mainText (textContent only — the house rule, no HTML strings);
the COUNT part gets countText when present (textContent +
hidden=false — the "(n/m)" position is visible, NEVER clipped)
and is emptied + hidden=true when absent; the CHUNK part
(phase 129) gets chunkText under the SAME contract (present →
the pinned "· chunk n/m" is visible, absent → emptied +
hidden=true — the part renders only while a file's embed batch
is in flight). Then the UNTRUNCATED full label — mainText +
(countText ? " " + countText : "") + (chunkText ? " " + chunkText
: "") — rides the button title + #sync-result (the aria-live
announcer) (A4 — the screen reader hears the whole "Syncing…
<file> (2/3) · chunk 14/38")."""
<file> (2/3) · chunk 14/38"). Phase 149 (task 01): the fourth
parameter `running` (the in-flight flag — the running entry
passes true, the settle the default false): the SAME full label
lands in the detail box (.sync-progress-text, textContent only)
and the box (#sync-progress) is visible IFF running (hidden in
every non-running state), and the button's accessible name is
the full label while running / the ship value "Sync sources"
otherwise (overwrites/restores only — never removes)."""
body = _fn(_js(), "setSyncLabel")
assert 'function setSyncLabel(mainText, countText = "", chunkText = "")' in body
assert (
'function setSyncLabel(mainText, countText = "", chunkText = "", running = false)'
in body
), "phase 149: the in-flight flag is the fourth parameter"
# the parts: textContent only (the house rule), null-guarded refs
assert "syncLabelMain.textContent = mainText" in body
assert "syncLabelCount.textContent = countText" in body
@@ -302,7 +330,13 @@ def test_set_sync_label_is_the_single_parts_writer() -> None:
i_full = body.find("const full")
i_title = body.find("syncBtn.title = full")
i_result = body.find("syncResult.textContent = full")
assert -1 < i_full < i_title < i_result, ("built once: full → title → announcer")
i_box = body.find("syncProgressText.textContent = full")
i_hide = body.find("syncProgress.hidden = !running")
i_aria = body.find('syncBtn.setAttribute("aria-label", running ? full : "Sync sources")')
assert -1 < i_full < i_title < i_result < i_box < i_hide < i_aria, (
"built once: full → title → announcer → the detail box (revealed IFF"
" running) → the button's accessible name (phase 149)"
)
assert "innerHTML" not in body, "the label path is textContent only"
@@ -421,16 +455,25 @@ def test_enter_running_state_is_phase_aware_and_writes_the_parts() -> None:
# the parts are handed to the single writer (A4 lives in it) —
# phase 129: the chunk part destructures with a "" default (the
# bare builders — the upload job, the 202/409 click — return no
# chunk part, so the part stays hidden there).
# chunk part, so the part stays hidden there); phase 149: the
# call passes `running = true` (the detail box reveals + the
# full-label aria-label ride in the writer).
assert "const { main, count, chunk = \"\" } =" in body, (
"the chunk part destructures with the '' default (bare builders return none)"
)
i_set = body.find("setSyncLabel(main, count, chunk)")
assert -1 < i_fall < i_set, "the parts are written after the split"
i_set = body.find("setSyncLabel(main, count, chunk, true)")
assert -1 < i_fall < i_set, "the parts are written after the split (running = true)"
assert body.count("setSyncLabel(") == 1, "one writer call per entry"
assert "syncBtn.title =" not in body, ("the title write lives in setSyncLabel (A4)")
assert "syncResult" not in body, ("the announcer write lives in setSyncLabel (A4)")
assert 'syncBtn.setAttribute("aria-label"' not in body, (
"the aria-label write lives in setSyncLabel (phase 149: the full"
" live label while running)"
)
assert "syncLabel.textContent" not in body, ("no direct span write — the parts")
assert "syncBtn.classList.add(\"is-running\")" in body, (
"the icon-only running button class is added with the spin (phase 149)"
)
def test_settle_and_idle_labels_go_through_the_parts_writer() -> None:
@@ -448,6 +491,12 @@ def test_settle_and_idle_labels_go_through_the_parts_writer() -> None:
assert "function settleSyncButton(label)" in settle
assert "setSyncLabel(label)" in settle, "the settle label goes through the parts writer"
assert settle.count("setSyncLabel(") == 1
assert "setSyncLabel(label, true)" not in settle, (
"the settle is NEVER a running state — the detail box hides (phase 149)"
)
assert "syncBtn.classList.remove(\"is-running\")" in settle, (
"the running class is removed with the spin on EVERY settle (phase 149)"
)
assert "syncLabel.textContent" not in settle, ("no direct span write — the parts")
# the success settle keeps the "Synced HH:MM" copy + the counts line
# AFTER the settle (the settle's announcer write is superseded by
@@ -487,6 +536,39 @@ def test_sync_label_markup_ships_the_hidden_chunk_part() -> None:
)
def test_sync_progress_box_markup_ships_under_the_button_row() -> None:
"""Phase 149 (task 01): #sync-progress — the sync detail box under
the button row (the shell's RAG view): between the .page-head block
(the button row + the page sub) and #sync-result. Ships hidden +
aria-hidden (the anonymous-safe/idle state — sources.js reveals it
through the single setSyncLabel writer only while a run is in
flight; presentational, so #sync-result stays the SOLE aria-live
announcer — one live region, no double announce). It carries one
.sync-progress-text span (id-pinned) — the writer's textContent
target, the full untruncated live label."""
html = _html()
box = re.search(
r'<div class="sync-progress" id="sync-progress" hidden aria-hidden="true">\s*'
r'<span class="sync-progress-text" id="sync-progress-text"></span>\s*</div>',
html,
)
assert box, "the shell must carry the #sync-progress detail box (phase 149)"
# Under the button row, above the announcer: the RAG view's
# .page-head block precedes the box, and the box precedes
# #sync-result (one live region — the box is presentational).
rag = html.find('id="view-rag"')
head = html.find('class="page-head"', rag)
result = html.find('id="sync-result"', rag)
assert -1 < head < box.start() < result, (
"the box sits between the RAG view's .page-head block and "
"#sync-result (directly under the button row, above the announcer)"
)
# Ship-hidden + presentational (the reveal is run-scoped, in JS).
tag = html[box.start() : html.find(">", box.start())]
assert re.search(r"\bhidden\b", tag), "the box ships hidden (idle / anonymous-safe)"
assert 'aria-hidden="true"' in tag, ("the box is presentational (no double announce)")
def test_sync_label_chunk_css_is_the_pinned_part() -> None:
""".sync-label-chunk (phase 129 task 04): the same pinned-part
language as .sync-label-count — flex 0 0 auto (NEVER clipped),
@@ -515,15 +597,33 @@ const syncLabelMain = { textContent: "" };
const syncLabelCount = { textContent: "", hidden: true };
const syncLabelChunk = { textContent: "", hidden: true };
const syncResult = { textContent: "" };
// Phase 149 (task 01): the detail box — presentational, ship-hidden;
// the REAL writer mutates it (full label + the running reveal).
const syncProgress = { hidden: true };
const syncProgressText = { textContent: "" };
// Phase 149 (task 01): class-tracking stubs — the is-running /
// is-spinning travel-together contract is behavioral, not source.
const syncBtn = {
disabled: false,
title: "",
attrs: {},
setAttribute(k, v) { this.attrs[k] = v; },
removeAttribute(k) { delete this.attrs[k]; },
classList: { add() {}, remove() {} },
classes: new Set(),
classList: {
add(c) { syncBtn.classes.add(c); },
remove(c) { syncBtn.classes.delete(c); },
has(c) { return syncBtn.classes.has(c); },
},
};
const syncIcon = {
classes: new Set(),
classList: {
add(c) { syncIcon.classes.add(c); },
remove(c) { syncIcon.classes.delete(c); },
has(c) { return syncIcon.classes.has(c); },
},
};
const syncIcon = { classList: { add() {}, remove() {} } };
// Phase 134 (task 02): the Cancel control — ships hidden + disabled
// (the markup's state); the extracted REAL show/hide helpers mutate
// it (show also sets the aria-label — the attrs pocket records it).
@@ -544,6 +644,11 @@ const show = (tag) => {
syncLabelCount.textContent, syncLabelCount.hidden,
syncLabelChunk.textContent, syncLabelChunk.hidden,
syncCancelBtn.hidden, syncCancelBtn.disabled,
// Phase 149 (task 01): the detail box (text + hidden), the
// is-running / is-spinning flags, the button's accessible name.
syncProgressText.textContent, syncProgress.hidden,
syncBtn.classes.has('is-running'), syncIcon.classes.has('is-spinning'),
syncBtn.attrs['aria-label'] || '',
]));
};
// 1. import, batch in flight (the task's fixture values, 14/38):
@@ -596,6 +701,34 @@ def _run_node(script: str) -> str:
return proc.stdout
def _chunk_harness_rows() -> list:
"""The node harness's rows — the REAL extracted label path against
stub element refs (the test-document-viewer pattern), one JSON row
per state: [tag, title, announcer, main, count, countHidden,
chunk, chunkHidden, cancelHidden, cancelDisabled, progressText,
progressHidden, btnRunning, iconSpinning, ariaLabel]. Shared by
the chunk-part test (phase 129) and the detail-box test
(phase 149)."""
js = _js()
fns = "\n".join(
_fn(js, name)
for name in (
"fmtSyncLabel",
"fmtSyncPhaseLabel",
"setSyncLabel",
# Phase 134 (task 02): the running entry's REAL cancel
# show/hide + the REAL settle (step 8) — extracted against
# the syncCancelBtn stub.
"showSyncCancelBtn",
"hideSyncCancelBtn",
"enterSyncRunningState",
"settleSyncButton",
)
)
script = _CHUNK_NODE_HEAD + fns + _CHUNK_NODE_TAIL
return [json.loads(line) for line in _run_node(script).splitlines() if line]
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_chunk_part_renders_only_in_the_import_phase_under_node() -> None:
"""Behavioral check (node) of the REAL label path — the extracted
@@ -620,27 +753,11 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
for the upload run (the upload job is NOT cancellable in this
phase) and after the (real) settle.
"""
js = _js()
fns = "\n".join(
_fn(js, name)
for name in (
"fmtSyncLabel",
"fmtSyncPhaseLabel",
"setSyncLabel",
# Phase 134 (task 02): the running entry's REAL cancel
# show/hide + the REAL settle (step 8) — extracted against
# the syncCancelBtn stub.
"showSyncCancelBtn",
"hideSyncCancelBtn",
"enterSyncRunningState",
"settleSyncButton",
)
)
script = _CHUNK_NODE_HEAD + fns + _CHUNK_NODE_TAIL
rows = [json.loads(line) for line in _run_node(script).splitlines() if line]
rows = _chunk_harness_rows()
by_tag = {row[0]: row for row in rows}
# row = [tag, title, announcer, main, count, countHidden, chunk,
# chunkHidden, cancelHidden, cancelDisabled]
# chunkHidden, cancelHidden, cancelDisabled, progressText,
# progressHidden, btnRunning, iconSpinning, ariaLabel]
# 1. the in-flight import — the chunk part renders, the full
# untruncated label rides the title + the announcer (A4).
@@ -694,6 +811,79 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
)
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_progress_box_and_running_button_contract_under_node() -> None:
"""Phase 149 (task 01) — the detail box + the icon-only running
button, behaviorally pinned through the SAME node harness (the
REAL setSyncLabel / enterSyncRunningState / settleSyncButton
against the stub element refs). The contract — the box is
visible IFF a run is in flight, and its text is EXACTLY the full
untruncated label (one source, two surfaces: the box for sighted
users, the announcer for AT):
* EVERY running state (the in-flight import, the pre-batch
import, the prelude, the 202/409 click, the upload run, the
overview, the summaries): the box is REVEALED (hidden=false)
and carries the full label (the same string as the button
title / announcer — the writer's `full`), the button carries
`is-running` + the icon `is-spinning` (the class travels with
the spin), and the button's accessible name (aria-label) is
the FULL live label (the icon-only running button keeps a
name);
* the settle (the REAL settleSyncButton through the writer with
the default `running = false`): the box HIDES, the settle
label is written into the box text ("Sync sources" — hidden,
so inert), `is-running` + `is-spinning` are both gone, and
the aria-label is restored to the ship value "Sync sources".
"""
rows = _chunk_harness_rows()
by_tag = {row[0]: row for row in rows}
# row = [tag, title, announcer, main, count, countHidden, chunk,
# chunkHidden, cancelHidden, cancelDisabled, progressText,
# progressHidden, btnRunning, iconSpinning, ariaLabel]
# Every running state: box visible + the EXACT full label +
# is-running + spinning + the full-label accessible name.
for tag in (
"import-inflight",
"import-prebatch",
"prelude",
"click",
"upload",
"overview",
"summaries",
):
row = by_tag[tag]
assert row[11] is False, (
f"{tag}: the detail box must be VISIBLE while the run is in flight"
)
assert row[10] == row[1], (
f"{tag}: the box carries the EXACT full label (title == box)"
)
assert row[12] is True, (
f"{tag}: the button must carry is-running while in flight"
)
assert row[13] is True, (
f"{tag}: is-running travels with the spin (the icon spins)"
)
assert row[14] == row[1], (
f"{tag}: the running button's accessible name is the full live label"
)
# The settle: the box hides, the running state is fully cleared,
# and the accessible name is back to the ship value.
row = by_tag["idle"]
assert row[11] is True, "the settle must HIDE the detail box (run-scoped only)"
assert row[10] == "Sync sources", (
"the writer wrote the settle label into the box (hidden — inert)"
)
assert row[12] is False, "is-running is removed on the settle (with the spin)"
assert row[13] is False, "the spin is removed on the settle"
assert row[14] == "Sync sources", (
"the settle restores the ship aria-label (the idle truth)"
)
def test_every_other_state_passes_no_chunk_text() -> None:
"""Source-level pin of the render-only-while-chunks_total>0
contract: the import fall-through is the ONLY place in sources.js
@@ -701,7 +891,8 @@ def test_every_other_state_passes_no_chunk_text() -> None:
branches and the bare builders never touch them), and every
NON-running label emission passes no chunk text (the part hides):
the settle/idle labels go through setSyncLabel(label) — the only
three-arg call site is the running entry."""
running-flagged call site (phase 149: `running = true`) is the
running entry."""
js = _js()
body = _fn(js, "fmtSyncPhaseLabel")
i_fall = body.find("fmtSyncLabel(")
@@ -711,7 +902,9 @@ def test_every_other_state_passes_no_chunk_text() -> None:
)
fall = body[i_fall:]
assert "status.chunks_total" in fall and "status.current_chunk" in fall
assert js.count("setSyncLabel(main, count, chunk)") == 1
assert js.count("setSyncLabel(main, count, chunk, true)") == 1, (
"the ONLY running-flagged call site is the running entry (phase 149)"
)
assert "setSyncLabel(label)" in js, "the settle/idle labels stay two-arg (no chunk)"
+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")