Files
brain-of-reese/.agents/phases/todo/128_sync_label_mobile/00_phase.md
T
ducoterra 0c8018971f chore(agent): phase roadmap from TODO.md, 7 phases (127-133)
Converts the TODO list into the phased-execution roadmap (Protocol B
append): incremental folder summaries on sync (127), the mobile sync
label with the pinned (n/m) count (128), per-chunk embed progress in
the sync label (129), chat answer clusters with the bottom-right
actions row (130), read(offset/limit) line ranges for huge documents
(131), visible chat image-upload feedback (132), and the garbage-PDF
text gate with the page-screenshot vision fallback (133 — includes
the diagnosed root cause of the hnb guide's pre-phase-126 binary
import). Each phase carries its dedicated in-isolation Playwright
suite; TODO.md is cleared (items now live in .agents/phases/todo/).

LLM-Generated: true
2026-09-27 12:34:16 -04:00

5.1 KiB
Raw Blame History

Phase 128 — The sync label shows on mobile, count always visible

Source: TODO.md L4 — "The Syncing… name/of/file/here (2/3) doesn't show up on mobile, it needs to" (the item is truncated in the TODO; ASSUMPTION A1 — read as: the running label must be fully visible on mobile, with the (n/m) position never clipped off-screen). Story: n/a (TODO-derived phase, Protocol B append). Context: frontend/index.html L449–451 — the #sync-btn / #sync-label markup (one span holds the whole label string); frontend/assets/sources.js — fmtSyncLabel(kind, currentFile, done, total) (~L650) and fmtSyncPhaseLabel(status) (~L669) build the running labels as ONE string, the poll loop applies them to #sync-label + the button title + #sync-result (the aria-live announcer — the untruncated text rides both, A4 convention); frontend/assets/styles.css — .sync-label (~L2067: inline-block, max-width: min(16rem, 40vw), ellipsis, nowrap) and the mobile media query (~L5323: .sync-btn { width: 100%; }, .sync-label { max-width: none; min-width: 0; } — with max-width: none the inline-block sizes to its FULL content, so the label's tail — where (n/m) lives — runs off-screen: the reported bug); tests/unit/test_frontend_sync_upload.py — the pinned frontend label tests to update.

Objective

On a phone, the running sync label is fully visible: the file path ellipsizes to fit, and the (n/m) position sits in its own non-shrinking span that is NEVER clipped — in the import phase (Syncing… <file> (2/3)) and the summaries phase (Summarizing folders… <folder> (n/m)). Desktop renders the same content with its existing visual layout (the cap, the ellipsis, the title/aria-live untruncated text all preserved).

Dependencies

  • 127_sync_incremental_folder_summaries (todo) — pipeline order only; code dependencies are the completed phase-64/98 label contract.

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

  • Markup (task 01): #sync-label gains two child spans — <span class="sync-label-main"></span><span class="sync-label-count" hidden></span> (static markup in index.html). The JS builders no longer return one string: a single setSyncLabel(mainText, countText) helper writes textContent into the parts (the createElement/textContent house rule — no HTML strings) and toggles the count span's hidden (absent count → hidden, empty). The FULL untruncated label (mainText + (countText ? " " + countText : "")) is still set on the button title and #sync-result exactly as today (A4 — screen readers hear the whole thing).
  • Label mapping (task 01): main/count per state — prelude (null phase, no file): main "Syncing…", no count; import: main "Syncing… <file>", count "(n/m)" while total > 0; overview: main "Writing KB overview…", no count; summaries: main "Summarizing folders… <folder>" (no folder part while current_summary is null), count "(n/m)"; upload: main "Importing", no count (phase-90 contract — the upload status carries no file or counts); idle/settle labels ("Sync sources", "Synced HH:MM"): main only. fmtSyncPhaseLabel's decision tree is preserved 1:1 — only the OUTPUT shape changes (string → parts).
  • CSS (task 02): .sync-label becomes a flex row (display: inline-flex; align-items: baseline; gap: 0.35rem; min-width: 0; max-width: min(16rem, 40vw);); .sync-label-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }; .sync-label-count { flex: 0 0 auto; white-space: nowrap; }. Mobile media query (the existing block at ~L5323): .sync-label { max-width: none; flex: 1 1 auto; min-width: 0; } — the label fills the full-width pill, the main part ellipsizes against the remaining width, the count span stays visible at the right edge. The #sync-result mobile clip (visually-hidden) is untouched.

Tasks

  1. 01_label_parts.md — markup + setSyncLabel part builders in sources.js (all label states, A4 untruncated text preserved).
  2. 02_mobile_css.md — the flex-part CSS + the mobile media query.
  3. 03_e2e_mobile_label.md — dedicated Playwright E2E, run in isolation.

Testing & Quality

  • Unit: tests/unit/test_frontend_sync_upload.py — the pinned label outputs updated to the part shape (main/count per state, the byte-identical label strings where they must hold: prelude, upload, settle); the title/#sync-result untruncated text pins stay.
  • E2E: one dedicated Playwright file, run in isolation, mobile viewport, slow-LLM pattern.
  • Coverage: >90% on new/modified code (frontend: the JS is covered by the pinned unit tests + the E2E; app/ untouched this phase).

Completion Criteria

  • at a phone viewport (390×844) the running label is fully in the viewport — file path ellipsized, (n/m) visible — in BOTH the import and summaries phases
  • desktop label visually unchanged (same strings, same cap/ellipsis, same title/#sync-result untruncated text)
  • uv run pytest tests/e2e/test_sync_label_mobile.py -v --no-cov green in isolation
  • uv run pytest (unit + integration) green; no behavior change in completed phases