Files
brain-of-reese/.agents/phases/complete/150_drop_sync_result/00_phase.md
T
ducoterra edf6048a0d
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_drop_sync_result
**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/`.
2026-10-02 02:35:03 -04:00

11 KiB
Raw Blame History

Phase 150 — retire span.sync-result: #sync-progress is the single sync text surface

Source: TODO.md L5 — "Get rid of the span sync-result now that we have the div sync-progress" Story: n/a (TODO-derived phase, Protocol B append). Context: the Sources page (frontend/index.html, the view-rag section). Phase 149 added #sync-progress (L493–495 — <div class="sync-progress" id="sync-progress" hidden aria-hidden="true"> + the inner <span class="sync-progress-text" id="sync-progress-text">), the bordered panel under the button row that carries the full untruncated live label while a run is in flight. The pre-149 announcer survived beside it: <span class="sync-result" id="sync-result" role="status" aria-live="polite"> (L511) — today the SOLE aria-live region, which receives (a) the same live label while a run runs (a byte-identical duplicate of the box), (b) the settle counts line (fmtSyncResult(detail) — "N added · M updated · …"), (c) the "Sync cancelled" line, (d) the cancel-request failure copy, (e) "" on the sync-failure / idle / upload-settle paths (plus the settle label "Synced HH:MM" and the idle "Sync sources", written through the shared writer). The single writer is frontend/assets/sources.js setSyncLabel(mainText, countText, chunkText, running) (L809–843): it writes the button parts, the title, the box text + hidden = !running, the button aria-label, AND syncResult.textContent = full (L834). The syncResult element handle is L750; the other write sites are L1092 (applySyncSuccess — the counts line), L1107 (applySyncIdle — clear), L1125 (applySyncCancelled — "Sync cancelled"), L1213/L1224 (the upload settles — clear), L1291/L1298/L1310 (the cancel paths — clear + "Couldn't cancel the sync — try again."). CSS: .sync-progress L2252, .sync-progress-text L2259, .sync-result L2284–2290, the mobile .sync-result override at L5639, and the "the sync-result shape" comments at L2938 / L3659 (those rules belong to OTHER elements — the upload success line and the action feedback line — and keep their own classes). Locked (owner decision, chat — this phase): #sync-progress becomes the single sync text surface AND the sole aria-live region: role="status" aria-live="polite" moves onto the box, aria-hidden is dropped, and span.sync-result is deleted from the template. The box keeps the phase-149 running behavior AND takes over the announcer's content: it is visible while a run is in flight (the live label) and stays visible after a settle to carry the result line (the counts / "Sync cancelled" / cancel-failure copy), hiding only when its text is empty (idle, upload settles, failures — exactly the states where #sync-result is empty today). Every string that reaches #sync-result today reaches the box instead — no copy changes.

Objective

One element, one live region: the sync/upload live label, the settle result line, the cancel line and the click copy all render in #sync-progress, which announces them exactly once; #sync-result no longer exists in the DOM, the JS, or the CSS.

Dependencies

  • 149_sync_progress_box (complete) — the box, its single-writer wiring, and its CSS are what this phase folds the announcer into. Pipeline order only: no phase is pending in todo/.

Design (shared by all tasks — the executor reads this, not the chat)

  • The element (task 01): #sync-progress keeps its id, class, and inner #sync-progress-text span (the textContent target — the house rule: text only, never HTML) and gains role="status" aria-live="polite; aria-hidden="true" is REMOVED (an aria-live region that is aria-hidden announces nothing — the two attributes are mutually exclusive by intent). It stays ship-hidden (hidden) — the anonymous-safe/idle state. span.sync-result (L511) and its explanatory comment block (L496–510) are deleted; the box's comment (L483–492) is rewritten to say it is now the SOLE announcer (the phase-149 "presentational (aria-hidden)" sentence is retired — leave a one-line note that phase 150 folded the announcer in, the file's pinned-comment convention).
  • The writer (task 02): introduce ONE function that owns the box — setSyncProgress(text) — which sets syncProgressText.textContent = text and syncProgress.hidden = (text === "") (the global [hidden] rule does the display). Two content modes, decided at the callsites (the same "the callsites are the only decision sites" rule phase 149 established):
    • running mode — setSyncLabel(main, count, chunk, running) keeps writing the box with full only when running === true (exactly today's reveal rule). When running === false it does NOT touch the box: the idle/settle button labels ("Sync sources", "Synced HH:MM") must not fill a big panel (today they land on the small #sync-result line where they read as a duplicate of the button — that artifact is retired, not migrated).
    • result mode — the settle paths call setSyncProgress(text) explicitly: applySyncSuccess → fmtSyncResult(status.detail) (the counts line), applySyncCancelled → "Sync cancelled", applySyncFailure → "" (the banner + modal carry the error), applySyncIdle → "", the two upload settles → "" (the upload result lives on the Sources page — phase 90 A3), requestCancel's network/non-2xx branches → "Couldn't cancel the sync — try again." (string unchanged). requestCancel's opening clear (L1291, which today blanks the announcer while the run is still in flight) becomes a NO-OP on the box — the live label stays up until the poll's cancelled branch settles it (blanking would hide the running surface for up to one 2 s poll). The syncResult handle (L750) and every if (syncResult) guard are deleted. There is no 202/409 click copy: startSync's 202/409 branch enters the running state (L1255–1262), so the box reveals the bare "Syncing…" label through setSyncLabel as today.
  • The CSS (task 03): delete the .sync-result rule (L2284–2290) and the mobile .sync-result override (L5639); the two "the sync-result shape" comments (L2938, L3659) are reworded to reference .sync-progress-text (they describe the mono/soft-ink language those other elements share — the rules themselves and their selectors are untouched). .sync-progress needs NO new rule for the settled state (the panel already reads as a calm box; the global [hidden] { display: none !important } rule keeps it gone when empty — verify it still applies, the box sets no display of its own). Contrast stays AA: .sync-progress-text uses --ink-soft on --surface (8.2:1 built-in dark, AA in both built-ins).
  • The test migration (tasks 04/05): mechanical selector migration — #sync-result → #sync-progress (assert on the box's text, i.e. #sync-progress-text for textContent, #sync-progress for hidden/attributes). Settled-state copy assertions must stay byte-identical (the counts line string itself does not change).
  • ASSUMPTION A1 (owner-locked): the box stays visible after settle to hold the counts line and hides only when its text is empty. The alternative (box running-only, result line elsewhere) was rejected.
  • ASSUMPTION A2: the settle label "Synced HH:MM" is no longer written to the live region (it stays on the button, visible). The announcer hears the counts line that superseded it in the same tick a moment ago — the merge makes one surface, and a panel reading "Synced HH:MM" under a button reading "Synced HH:MM" is the duplicate this retirement exists to remove.
  • ASSUMPTION A3: the box's settled appearance needs no new CSS (the same bordered panel reads fine for a result line); only the running-only min-height stays, so the panel does not jump between modes.

Tasks

  1. 01_html_single_surface.md — delete span#sync-result, move role=status aria-live=polite onto #sync-progress, drop aria-hidden, rewrite the two comment blocks.
  2. 02_js_single_writer.md — sources.js: add setSyncProgress, route all 9 announcer sites through it, delete the syncResult handle, change the box visibility rule to text-driven.
  3. 03_css_retire_sync_result.md — styles.css: remove .sync-result + its mobile override, reword the two shape comments.
  4. 04_unit_test_migration.md — tests/unit/test_sync_button.py (20 refs) + tests/unit/test_frontend_sync_upload.py (26 refs) migrated to the box.
  5. 05_e2e_sync_family.md — the 12 sync-family E2E files' selectors migrated + the dedicated tests/e2e/test_sync_progress_box.py extended with the single-live-region + settled-line assertions.
  6. 06_e2e_image_pdf_family.md — the 6 image/PDF E2E files migrated (their sync-done barriers move to the box) + the a11y/no-CDN/full-gate pass and the phase commit.

Testing & Quality

  • Unit: the two frontend-parsing suites (test_sync_button.py, test_frontend_sync_upload.py) pin the writer contract on the box — running label visible + announced, settle counts line, "Sync cancelled", the idle/upload clears, the "Couldn't cancel" copy — and assert #sync-result is ABSENT from the template (the retirement is pinned, not implied).
  • E2E: uv run pytest tests/e2e/test_sync_progress_box.py -v --no-cov in isolation (the slow-LLM proxy recipe already in that file) proves in-flight → settle → idle on the real sync path; every pre-existing sync/image/PDF E2E that touched the announcer passes after migration (12 sync-family files + 6 image/PDF files).
  • Coverage: >90% on app/ (this phase touches no app/ logic — the floor must simply not regress).
  • Accessibility gate (AGENTS.md rule 5): exactly ONE aria-live region in the sync area; the box is a status region (polite — no alert spam); the button keeps its accessible name in both states; focus-visible and contrast unchanged.

Completion Criteria

  • grep -rn "sync-result\|syncResult" frontend/ tests/ returns NOTHING (the element, its class, and its JS handle are gone from source and tests)
  • #sync-progress carries role="status" aria-live="polite" and no aria-hidden; it is the only aria-live element in the sync area
  • in flight: the box shows the full untruncated live label (wrapping, never clipped); at settle: the box shows the counts line byte-identical to the pre-phase #sync-result string ("N added · …") and "Sync cancelled" for a cancel; at idle / upload-settle / sync-failure: the box is hidden and empty
  • the button is still the icon-only spinner while running and restores "Sync sources" on settle (phase 149 behavior unchanged)
  • uv run pytest green; uv run pytest tests/e2e/test_sync_progress_box.py -v --no-cov green in isolation; uv run ruff check . && uv run pyright clean; app/ coverage >90%
  • no behavior change in completed phases (the cancel control, the 409 double-trigger, the error banner/modal, the upload run's status, the sync status API)
  • one atomic commit per AGENTS.md rule 8 (--no-gpg-sign)