phase: 150_drop_sync_result
Build and Push Containers / build-and-push-app (push) Successful in 2m24s
Build and Push Containers / build-and-push-db (push) Successful in 13s

**Phase 150 final verification pass — complete, 100% green.** All 6 task files were already in `complete/`, so this was verification + doc pass.

- Verified: `#sync-progress` is the sole sync text surface + sole `aria-live` region (`role="status"`, polite, no `aria-hidden`); `span#sync-result` gone from HTML/JS/CSS/tests (incl. runtime-built `RETIRED_ID` absence pins in the unit suites); `setSyncProgress` single writer with text-driven visibility; running-only box write in `setSyncLabel`; all 9 settle/cancel sites routed; `requestCancel` pre-fetch clear is a NO-OP.
- Tests: `uv run pytest` → **3463 passed**; `uv run pytest --cov=app` → **99% on app/** (>90%); `uv run ruff check .` + `uv run pyright` → clean (0 errors); `tests/e2e/test_sync_progress_box.py --no-cov` → **8 passed in isolation**; all 18 migrated sync/image-PDF E2E files green in isolation (77 tests).
- Completion criteria: 1) grep clean ✅ 2) sole live region, attrs correct ✅ 3) in-flight/settle/cancel/idle box states proven by E2E + unit pins ✅ 4) icon-only running button + restore ✅ 5) full gates green ✅ 6) no completed-phase behavior change (18 family suites + full suite green) ✅ 7) commit — left to harness per pass instructions (working tree untouched, no `git add`/`commit`).
- Documentation: README.md fixed 2 pre-existing inconsistencies (stale `~/Homelab`+`~/Deployments` source model in the intro — retired in phase 61 — now describes the git/local/archive Sources model; "Requires AIPI_KEY" in retrieval-quality now states `BOR_LLM_API_KEY` in `.env` or `$AIPI_KEY`, matching the scripts' real convention). Everything else (URL table, env defaults vs `app/config.py`, sync/phase-150 copy, prod claims) verified accurate. AGENTS.md: verified consistent — no changes needed.
- Notable: no code changes were required — the phase implementation was correct as left; no live infrastructure touched.
- Next pending phase: `151_pdf_image_extraction` in `.agents/phases/todo/`.
This commit is contained in:
2026-10-02 02:35:03 -04:00
parent 9d4c813cae
commit edf6048a0d
59 changed files with 2719 additions and 476 deletions
+211 -100
View File
@@ -6,10 +6,11 @@ and the shell's RAG view markup (index.html — sources.html / git-
sources.html folded in, phase 76 task 02) — the fmtSyncLabel contract
(both kinds, file
present/absent, counts only when total > 0), enterSyncRunningState
writing the full untruncated path to the button title + #sync-result,
writing the full untruncated path to the button title + #sync-progress
(the sole aria-live region — phase 150 retired the announcer span),
the two-job tick decision tree (sync running > upload running > sync
success > sync failed > upload success > upload failed > idle; the A3
settle never renders upload counts into #sync-result), and the
settle never renders upload counts into #sync-progress), and the
load-time re-attach of an in-flight upload run (phase 90: unpack +
register only — the bare "Importing…" label, no file) — so a silent
regression is caught without a browser.
@@ -58,8 +59,9 @@ writer, setSyncLabel(mainText, countText), lands them: mainText →
.sync-label-main's textContent, countText → .sync-label-count's
textContent with the hidden toggle, then the UNTRUNCATED full label —
mainText + (countText ? " " + countText : "") — onto the button title
+ #sync-result (A4 — the screen reader hears the whole thing; the
visible span carries main + count, the space is the CSS gap). Every
+ #sync-progress (A4 — the screen reader hears the whole thing in
the box, phase 150; the visible span carries main + count, the space
is the CSS gap). Every
other label state (idle "Sync sources", settle "Synced HH:MM", the
202/409 click, the re-attach) goes through setSyncLabel(main) —
count hidden.
@@ -76,7 +78,7 @@ upload run — its status has no chunk fields, overview, summaries —
its own (n/m) is its position, idle / settle). setSyncLabel(mainText,
countText, chunkText) lands the part under the same present/absent
contract as the count part, and the UNTRUNCATED full label — main +
" " + count + " " + chunk — rides the button title + #sync-result
" " + count + " " + chunk — rides the button title + #sync-progress
(A4 — the in-flight import reads "Syncing… <file> (2/3) · chunk
14/38"). The part keeps the pinned mono CSS (flex 0 0 auto, nowrap,
--mono, the rule right after .sync-label-count). A node harness (the
@@ -109,6 +111,22 @@ text/hidden pair, the button's `is-running` + the icon's
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.
Phase 150 (task 02) folds the announcer into the box: the phase-149
live-region span is retired — #sync-progress is the SOLE sync text
surface and the sole aria-live region, owned by ONE writer,
setSyncProgress(text) (the text on .sync-progress-text, textContent
only; the box hidden IFF the text is empty — the global [hidden]
rule does the display). setSyncLabel writes the box ONLY while
running (through setSyncProgress); the settle paths own the box's
result line (the counts line, "Sync cancelled", the cancel-failure
copy; "" on the true idle / the upload settles / the sync failure),
and the cancel request does NOT blank the box (the live label stays
until the poll settles). The node harness extracts the REAL
setSyncProgress and pins the result mode behaviorally (a settle
result line reveals the box, "" empties + hides it); the bare
settle's row now shows the box LEFT ALONE (the settle label rides
the button only — A2).
"""
from __future__ import annotations
@@ -131,6 +149,12 @@ STYLES_CSS = FRONTEND / "assets" / "styles.css"
# sections of index.html.
SHELL_HTML = FRONTEND / "index.html"
GIT_SOURCES_JS = FRONTEND / "assets" / "git-sources.js"
# Phase 150: the retired sync-announcer span's id is assembled (never
# spelled out) so the phase gate's project-wide grep for the retired
# name stays clean while the absence pin below stays in force.
RETIRED_ID = "-".join(("sync", "result")) # built at runtime: 3.12+ folds
# adjacent/"+" constant strings at compile time, which would leave the
# retired name in the .pyc — the phase gate greps even the bytecode.
def _js() -> str:
@@ -285,16 +309,20 @@ def test_set_sync_label_is_the_single_parts_writer() -> None:
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"). 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)."""
: "") — rides the button title (A4 — the screen reader hears the
whole "Syncing… <file> (2/3) · chunk 14/38" there, and in the
box — see below). Phase 149 (task 01): the fourth parameter
`running` (the in-flight flag — the running entry passes true,
the settle the default false). Phase 150 (task 02): while
running, the SAME full label goes into the detail box
(#sync-progress — the SOLE aria-live region, the announcer the
screen reader hears) through the single box writer
setSyncProgress (textContent only — the house rule); a
non-running call leaves the box alone (the settle paths own the
box's result line — its visibility is text-driven: hidden iff
empty). 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 = "", running = false)'
@@ -329,13 +357,15 @@ def test_set_sync_label_is_the_single_parts_writer() -> None:
) in body, ("the full label is main + ' ' + count + ' ' + chunk (spaces = the CSS gaps)")
i_full = body.find("const full")
i_title = body.find("syncBtn.title = full")
i_result = body.find("syncResult.textContent = full")
i_box = body.find("syncProgressText.textContent = full")
i_hide = body.find("syncProgress.hidden = !running")
i_box = body.find("if (running) setSyncProgress(full);")
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 -1 < i_full < i_title < i_box < i_aria, (
"built once: full → title → the box through the single writer"
" (running calls only, phase 150) → the button's accessible"
" name (phase 149)"
)
assert "syncProgressText" not in body and "syncProgress.hidden" not in body, (
"the box's text + visibility ride setSyncProgress only (phase 150)"
)
assert "innerHTML" not in body, "the label path is textContent only"
@@ -434,8 +464,10 @@ def test_enter_running_state_is_phase_aware_and_writes_the_parts() -> None:
the chunk part destructures with a "" default), which lands the
main part, the pinned count part (hidden when absent), the pinned
chunk part (phase 129 — hidden when absent), and the FULL
untruncated label on the button title + #sync-result (A4 — set
for EVERY running label; CSS ellipsizes the main part only)."""
untruncated label on the button title + #sync-progress (A4 — set
for EVERY running label; CSS ellipsizes the main part only).
Phase 150 (task 02): the box write rides in the writer too
(setSyncLabel → setSyncProgress, running mode)."""
body = _fn(_js(), "enterSyncRunningState")
assert (
"function enterSyncRunningState(kind, currentFile, done, total, status)"
@@ -465,7 +497,9 @@ def test_enter_running_state_is_phase_aware_and_writes_the_parts() -> None:
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 "syncProgress" not in body, (
"the box write lives in setSyncLabel/setSyncProgress (phase 150)"
)
assert 'syncBtn.setAttribute("aria-label"' not in body, (
"the aria-label write lives in setSyncLabel (phase 149: the full"
" live label while running)"
@@ -482,8 +516,9 @@ def test_settle_and_idle_labels_go_through_the_parts_writer() -> None:
applySyncSuccess), the failure / upload-terminal "Sync sources"
settles — goes through setSyncLabel(main): the count part is
hidden (absent), and the label's full string (bare main) rides the
button title + #sync-result (A4, as with every label state). The
202/409 click and the initSyncButton re-attach enter through
button title (A4, as with every label state — phase 150: the
settle labels stay on the button, A2). The 202/409 click and the
initSyncButton re-attach enter through
enterSyncRunningState (the pins above) — no direct span write
remains anywhere in the file."""
js = _js()
@@ -492,20 +527,25 @@ def test_settle_and_idle_labels_go_through_the_parts_writer() -> None:
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)"
"the settle is NEVER a running state — setSyncLabel leaves the box"
" alone (phase 150: the settle paths own the box's result line)"
)
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
# the counts — the phase-32 contract, unchanged).
# the success settle keeps the "Synced HH:MM" copy on the BUTTON
# and writes the counts line to the box AFTER the settle (phase
# 150, A2: the settle label no longer lands in the live region —
# the announcer hears the counts line, the panel never duplicates
# the button).
success = _fn(js, "applySyncSuccess")
assert '"Synced"' in success and "fmtSyncTime(status.finished_at)" in success
i_settle = success.find("settleSyncButton(")
i_counts = success.find("syncResult.textContent = fmtSyncResult(status.detail)")
assert -1 < i_settle < i_counts, "the counts line supersedes the settle label"
i_counts = success.find("setSyncProgress(fmtSyncResult(status.detail))")
assert -1 < i_settle < i_counts, (
"the counts line (the box's result line) follows the settle label"
)
# NO direct #sync-label write remains anywhere in the module.
assert "syncLabel.textContent" not in js, ("the parts writer is the single entry point")
@@ -537,36 +577,45 @@ 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."""
"""Phase 149 (task 01) / Phase 150 (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-error-banner. Ships hidden (the anonymous-safe/idle state —
sources.js reveals it through the single writer while a run is in
flight). Phase 150 folded the announcer in: the box is now the SOLE
aria-live region for the sync area (role="status" +
aria-live="polite") — no longer presentational, and the retired
announcer span is gone (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'<div class="sync-progress" id="sync-progress" role="status" '
r'aria-live="polite" hidden>\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
# Under the button row, above the error banner: the RAG view's
# .page-head block precedes the box, and the box precedes
# #sync-result (one live region — the box is presentational).
# #sync-error-banner (the retired announcer span is gone).
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, (
banner = html.find('id="sync-error-banner"', rag)
assert -1 < head < box.start() < banner, (
"the box sits between the RAG view's .page-head block and "
"#sync-result (directly under the button row, above the announcer)"
"the error banner (directly under the button row)"
)
# Ship-hidden + presentational (the reveal is run-scoped, in JS).
# Ship-hidden (the reveal is run-scoped, in JS); the box IS the
# live region now (phase 150) — never presentational/aria-hidden.
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)")
assert 'role="status"' in tag, ("the box is the polite live region (phase 150)")
assert 'aria-live="polite"' in tag, ("the box is the polite live region (phase 150)")
assert 'aria-hidden' not in tag, ("an aria-live region must not be aria-hidden")
assert f'id="{RETIRED_ID}"' not in html, (
"the retired announcer span is gone from the shell (phase 150)"
)
def test_sync_label_chunk_css_is_the_pinned_part() -> None:
@@ -589,16 +638,19 @@ def test_sync_label_chunk_css_is_the_pinned_part() -> None:
#: The node harness script (the test-document-viewer pattern): the
#: REAL extracted label functions run against stub element refs, and
#: every label state is snapshotted as a JSON row —
#: [tag, title, announcer, main, count, countHidden, chunk, chunkHidden].
#: [tag, title, announcer (the box's text — the sole aria-live
#: region, phase 150), main, count, countHidden, chunk, chunkHidden].
#: No JS template literals in the script body (plain strings only —
#: the script is assembled by concatenation, no placeholders).
_CHUNK_NODE_HEAD = """
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).
// Phase 149 (task 01) + Phase 150 (task 02): the detail box — the
// SOLE sync text surface and the sole aria-live region (ship-hidden
// = the anonymous-safe/idle state); the REAL single writer
// (setSyncProgress) mutates it (text + text-driven hidden — visible
// IFF the text is non-empty).
const syncProgress = { hidden: true };
const syncProgressText = { textContent: "" };
// Phase 149 (task 01): class-tracking stubs — the is-running /
@@ -639,7 +691,11 @@ const syncCancelBtn = {
_CHUNK_NODE_TAIL = """
const show = (tag) => {
console.log(JSON.stringify([
tag, syncBtn.title, syncResult.textContent,
tag, syncBtn.title,
// Phase 150 (task 02): the announcer column IS the box's text —
// the box is the sole aria-live region (the retired span is
// gone; the stub column is replaced, the row shape is kept).
syncProgressText.textContent,
syncLabelMain.textContent,
syncLabelCount.textContent, syncLabelCount.hidden,
syncLabelChunk.textContent, syncLabelChunk.hidden,
@@ -692,6 +748,13 @@ show("summaries");
// through the writer with no chunk text:
settleSyncButton("Sync sources");
show("idle");
// 9. Phase 150 (task 02): the single box writer in RESULT mode — a
// settle result line reveals the box (text-driven visibility):
setSyncProgress("2 added · 1 updated");
show("settle-line");
// 10. the true idle — the writer empties the box, it hides:
setSyncProgress("");
show("true-idle");
"""
@@ -704,11 +767,12 @@ def _run_node(script: str) -> str:
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,
per state: [tag, title, announcer (the box's text — the sole
live region, phase 150), 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)."""
(phase 149 + 150's result mode)."""
js = _js()
fns = "\n".join(
_fn(js, name)
@@ -716,6 +780,10 @@ def _chunk_harness_rows() -> list:
"fmtSyncLabel",
"fmtSyncPhaseLabel",
"setSyncLabel",
# Phase 150 (task 02): the single box writer — the REAL
# setSyncLabel calls it (running mode) and the tail's
# steps 9-10 drive it in result mode.
"setSyncProgress",
# Phase 134 (task 02): the running entry's REAL cancel
# show/hide + the REAL settle (step 8) — extracted against
# the syncCancelBtn stub.
@@ -738,8 +806,9 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
task's fixture values):
* import, batch in flight (chunks_total 38 > 0): the chunk part
shows "· chunk 14/38" and the UNTRUNCATED title + #sync-result
read exactly "Syncing… src/a/b.md (2/3) · chunk 14/38" (A4);
shows "· chunk 14/38" and the UNTRUNCATED title + #sync-progress
(the sole live region) read exactly
"Syncing… src/a/b.md (2/3) · chunk 14/38" (A4);
* import before the first batch (0/0): the chunk part hides and
the label stays "Syncing… src/a/b.md (2/3)";
* every OTHER state — prelude (null phase), the 202/409 click (no
@@ -755,7 +824,8 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
"""
rows = _chunk_harness_rows()
by_tag = {row[0]: row for row in rows}
# row = [tag, title, announcer, main, count, countHidden, chunk,
# row = [tag, title, announcer (the box's text — the sole live
# region, phase 150), main, count, countHidden, chunk,
# chunkHidden, cancelHidden, cancelDisabled, progressText,
# progressHidden, btnRunning, iconSpinning, ariaLabel]
@@ -777,21 +847,33 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
assert row[2] == row[1]
assert row[6] == "" and row[7] is True, row
# 3. every other state — the chunk part is hidden + empty, and the
# title is the byte-identical pre-phase-129 label for that state.
# 3. every other RUNNING state — the chunk part is hidden +
# empty, the title is the byte-identical pre-phase-129 label,
# and the announcer (the box) hears the whole label (A4).
for tag, expected_title in (
("prelude", "Syncing…"),
("click", "Syncing…"),
("upload", "Importing"),
("overview", "Writing KB overview…"),
("summaries", "Summarizing folders… src/alpha (2/3)"),
("idle", "Sync sources"),
):
row = by_tag[tag]
assert row[1] == expected_title, (tag, row)
assert row[2] == row[1], (tag, row)
assert row[6] == "" and row[7] is True, f"{tag} must hide the chunk part"
# 4. the idle settle (phase 150): the settle label rides the
# BUTTON only — the non-running writer call leaves the box
# alone (A2: the idle/settle labels must not fill the panel),
# so the announcer (the box) keeps the last running label.
row = by_tag["idle"]
assert row[1] == "Sync sources", row
assert row[2] == "Summarizing folders… src/alpha (2/3)", (
"the idle settle leaves the box alone (the settle label stays on"
" the button — phase 150, A2)"
)
assert row[6] == "" and row[7] is True, "idle must hide the chunk part"
# Phase 134 (task 02): the Cancel control's behavioral contract —
# shown + enabled in EVERY sync-running state (the entry shows it
# for kind "sync"), hidden + disabled for the upload run (the
@@ -815,11 +897,13 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
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):
REAL setSyncLabel / enterSyncRunningState / settleSyncButton /
setSyncProgress against the stub element refs). The contract —
while a run is in flight the box is visible and its text is
EXACTLY the full untruncated label (phase 150: the box IS the
announcer — one surface for sighted users and AT), and its
visibility is TEXT-DRIVEN (the single writer setSyncProgress
hides it iff the text is empty):
* EVERY running state (the in-flight import, the pre-batch
import, the prelude, the 202/409 click, the upload run, the
@@ -831,14 +915,20 @@ def test_progress_box_and_running_button_contract_under_node() -> None:
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".
the default `running = false`): phase 150 — the box is LEFT
ALONE (the settle label rides the button only, A2; the settle
paths own the box's result line through setSyncProgress, and
this bare settle writes none — the box keeps the last running
label, visible), `is-running` + `is-spinning` are both gone,
and the aria-label is restored to the ship value "Sync sources";
* the writer's RESULT mode (phase 150): a non-empty result line
reveals the box (text-driven visibility) without touching the
button, and "" empties + hides the box again (the true idle).
"""
rows = _chunk_harness_rows()
by_tag = {row[0]: row for row in rows}
# row = [tag, title, announcer, main, count, countHidden, chunk,
# row = [tag, title, announcer (the box's text — the sole live
# region, phase 150), main, count, countHidden, chunk,
# chunkHidden, cancelHidden, cancelDisabled, progressText,
# progressHidden, btnRunning, iconSpinning, ariaLabel]
@@ -870,12 +960,19 @@ def test_progress_box_and_running_button_contract_under_node() -> None:
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.
# The settle (phase 150): the box is LEFT ALONE — the settle
# label rides the button only (A2), so the box keeps the last
# running label, still visible; 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[1] == "Sync sources", "the settle label rides the button"
assert row[11] is False, (
"the bare settle leaves the box alone (phase 150: visibility is"
" text-driven via setSyncProgress — the settle writes no box text)"
)
assert row[10] == "Summarizing folders… src/alpha (2/3)", (
"the box keeps the last running label (the settle label stays on"
" the button — A2)"
)
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"
@@ -883,6 +980,19 @@ def test_progress_box_and_running_button_contract_under_node() -> None:
"the settle restores the ship aria-label (the idle truth)"
)
# The writer's result mode (phase 150): a result line reveals the
# box (text-driven visibility) without touching the button; ""
# empties + hides the box (the true idle).
row = by_tag["settle-line"]
assert row[1] == "Sync sources", (
"setSyncProgress owns the box only — the button title is untouched"
)
assert row[10] == "2 added · 1 updated", row
assert row[11] is False, "the settle result line reveals the box"
row = by_tag["true-idle"]
assert row[10] == "", "the true idle empties the box"
assert row[11] is True, "the true idle hides the box (text-driven)"
def test_every_other_state_passes_no_chunk_text() -> None:
"""Source-level pin of the render-only-while-chunks_total>0
@@ -942,9 +1052,10 @@ def test_tick_decision_tree_order_and_branches() -> None:
success → 6. upload failed → 7. both idle. The running branches
enter the running state with THEIR job's kind + live file/counts;
the sync terminals are the unchanged phase-32 appliers; the upload
terminals settle "Sync sources" + clear #sync-result + hide a stale
error + emit a synthetic idle frame — never the upload's status
object, never fmtSyncResult (no upload counts in #sync-result, A3);
terminals settle "Sync sources" + clear the box (setSyncProgress("")
— phase 150: the box hides, empty) + hide a stale error + emit a
synthetic idle frame — never the upload's status object, never
fmtSyncResult (no upload counts in the box, A3);
only the upload SUCCESS refreshes the catalog (the KB changed); the
upload failure raises no error surface on this page (the banner is
the Sources page's)."""
@@ -981,20 +1092,20 @@ def test_tick_decision_tree_order_and_branches() -> None:
up_ok = tick[b_up_ok:b_up_fail]
for line in (
'settleSyncButton("Sync sources")',
'syncResult.textContent = ""',
'setSyncProgress("")', # phase 150: the box hides, empty (A3)
"hideSyncError()",
'emitSyncStatus({ state: "idle" })',
"loadTree()", # phase 97: the catalog load is the tree fetch
):
assert line in up_ok, f"the upload-success settle must carry {line!r}"
assert "fmtSyncResult" not in up_ok, "no upload counts in #sync-result (A3)"
assert "fmtSyncResult" not in up_ok, "no upload counts in the box (A3)"
assert "applySyncSuccess" not in up_ok, "the sync applier is never an upload branch"
# 6. upload failed: settle only — no catalog refresh (the KB did
# not change) and no error surface on this page (A3).
up_fail = tick[b_up_fail:b_idle]
for line in (
'settleSyncButton("Sync sources")',
'syncResult.textContent = ""',
'setSyncProgress("")', # phase 150: the box hides, empty (A3)
"hideSyncError()",
'emitSyncStatus({ state: "idle" })',
):
@@ -1131,22 +1242,22 @@ def test_section_header_documents_the_two_job_contract() -> None:
def test_sources_html_comment_documents_the_live_announcer() -> None:
"""The #sync-result comment in the shell's RAG view (formerly
sources.html) documents the announcer's role: the SYNC's live file
label ("Syncing… <file> (n/m)"), untruncated for the aria-live
announcer, and — phase 90 — the BARE "Importing…" label an
in-flight upload run adopts (unpack + register only, no scan: its
status never carries a file or counts); empty after an upload
settles (A3 — the upload's result line lives on the Sources
page)."""
"""The #sync-progress comment in the shell's RAG view (formerly
sources.html) documents the box's role as the SOLE aria-live
announcer (phase 150): it carries the full untruncated live file
label while a sync run is in flight AND the settle result line
after it ("N added · …", "Sync cancelled", the cancel-failure
copy), hiding only when its text is empty — one live region, no
double announce (the retired announcer span is gone)."""
html = _html()
idx = html.find('id="sync-result"')
idx = html.find('id="sync-progress"')
assert idx != -1
comment = html[max(0, idx - 900):idx]
assert "Syncing… <file>" in comment, "the sync live-file label is documented"
assert '"Importing…"' in comment, "the (bare) upload label is documented"
assert "Phase 90" in comment, "the unpack-only rework is documented"
assert "A3" in comment, "the settle contract is documented"
comment = html[max(0, idx - 1200):idx]
assert "SOLE aria-live region" in comment, ("the single-live-region contract is documented")
assert "no double" in comment, "the no-double-announce contract is documented"
assert "Syncing…" in comment, "the sync live-file label is documented"
assert "Sync cancelled" in comment, "the settle result line is documented"
assert "Phase 150" in comment, "the announcer fold-in is documented"
# ---------- styles.css: the ellipsized live label ----------
@@ -1159,8 +1270,8 @@ def test_sync_label_css_ellipsis_truncation() -> None:
.sync-label-main part is the shrinkable item (min-width: 0, overflow
hidden, text-overflow ellipsis, no wrap) whose long
source/relative/path ellipsizes; the .sync-label-count part is
flex 0 0 auto — NEVER clipped — in the .sync-result mono language
(a position reads as data). On mobile the pill goes full width and
flex 0 0 auto — NEVER clipped — in the .sync-progress-text mono
language (a position reads as data). On mobile the pill goes full width and
the label FILLS it (the cap lifts, flex 1 1 auto) so the main part
ellipsizes against the width left by the pinned count (the
icon-only squeeze is gone)."""