phase: 193_sync_remaining_count
**Phase 193 — final verification pass: all gates green.**
- **Verified (all 3 tasks already complete in `complete/193_sync_remaining_count/`):** `fmtSyncLabel`'s count part is `total > 0 && total - done > 0 ? \`${total - done} remaining\` : ""` (plain int, hidden at `N==0`, `total===0` → no part); docstrings updated; chunk/image/summaries parts + `counts_line` terminal + `app/` byte-untouched (`git diff --stat`: no `app/` files).
- **Completion criteria:** `N remaining` shape + marker pins ✅ · upload shares the builder (E2E upload arm) ✅ · chunk/image/summaries pins green ✅ · terminal `counts_line` green unedited + A4 title/`#sync-progress` pin ✅ · suite green, coverage 99% ✅ · lint/types clean ✅ · commit + `todo/→complete/` left to the harness (no git index/history commands run).
- `uv run pytest -q --cov=app --cov-report=term-missing` → **exit 0**, junit: **4792 tests, 0 failures/errors/skipped**, `TOTAL 7181 16 → 99%` (>90%)
- `uv run pytest tests/e2e/test_sync_remaining_count.py -v --no-cov` → **3 passed** (isolated, 53 s)
- Isolated `--no-cov` sweep of the 14 sync-lineage suites (label_mobile, upload_progress, chunk_progress, image_progress, progress_box, summary_visibility, archive_upload_sources, status_freshness, upload_no_scan, peak_memory_visible, session_reset, fast_path_deferred_content, git_freeze, smoke) → **14/14 exit 0**
- `uv run ruff check .` → All checks passed · `uv run pyright` → **0 errors**
- **Docs:** README.md reviewed in full (all 2408 lines) + AGENTS.md. Fixed 2 inconsistencies: the sync running-label clause claimed the upload label shows the count — now states it shares the ONE builder/shape but reports no counts since phase 90 (renders bare); phase-190's "the other ~72 rows" → "the other 83 rows of the panel's 86-row catalogue" (mechanically: 86 CATALOG rows, 84 overridable). `N remaining` already in the sync paragraph; task-01's `(n/m)` examples gone. AGENTS.md verified consistent (no clause names the running count part → unchanged per the phase's "do not add if absent"). Mechanically re-verified: all 86 `BOR_*` settings documented + defaults match, `.env.example` 88 vars covered, 0 broken README anchors, all cited file paths exist, 17 palette colors, migrations head `0030`, pages/shell routes, ports 8123/8901, E2E port allocations (new suite's 8211/8914 unique).
- **Notable:** task 02's documented deviation stands — `files_done` is the importer's 1-based *current* file, so the phase doc's `[1-4]` range is unreachable; count-bearing frames are `1 … N−1` (pinned). Pre-existing, untouched: `E2E_APP_PORT_TOOLBUDGET`/`SAVEDOC` both 8129 and `SYNCUP`/`SYCNCEL` both 8133 — harmless under the one-suite-at-a-time rule.
- **Next pending phase:** none — `193_sync_remaining_count` is the last entry in `.agents/phases/todo/`.
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
**Phase 193 — final verification pass: all gates green.**
|
||||||
|
|
||||||
|
- **Verified (all 3 tasks already complete in `complete/193_sync_remaining_count/`):** `fmtSyncLabel`'s count part is `total > 0 && total - done > 0 ? \`${total - done} remaining\` : ""` (plain int, hidden at `N==0`, `total===0` → no part); docstrings updated; chunk/image/summaries parts + `counts_line` terminal + `app/` byte-untouched (`git diff --stat`: no `app/` files).
|
||||||
|
- **Completion criteria:** `N remaining` shape + marker pins ✅ · upload shares the builder (E2E upload arm) ✅ · chunk/image/summaries pins green ✅ · terminal `counts_line` green unedited + A4 title/`#sync-progress` pin ✅ · suite green, coverage 99% ✅ · lint/types clean ✅ · commit + `todo/→complete/` left to the harness (no git index/history commands run).
|
||||||
|
- `uv run pytest -q --cov=app --cov-report=term-missing` → **exit 0**, junit: **4792 tests, 0 failures/errors/skipped**, `TOTAL 7181 16 → 99%` (>90%)
|
||||||
|
- `uv run pytest tests/e2e/test_sync_remaining_count.py -v --no-cov` → **3 passed** (isolated, 53 s)
|
||||||
|
- Isolated `--no-cov` sweep of the 14 sync-lineage suites (label_mobile, upload_progress, chunk_progress, image_progress, progress_box, summary_visibility, archive_upload_sources, status_freshness, upload_no_scan, peak_memory_visible, session_reset, fast_path_deferred_content, git_freeze, smoke) → **14/14 exit 0**
|
||||||
|
- `uv run ruff check .` → All checks passed · `uv run pyright` → **0 errors**
|
||||||
|
- **Docs:** README.md reviewed in full (all 2408 lines) + AGENTS.md. Fixed 2 inconsistencies: the sync running-label clause claimed the upload label shows the count — now states it shares the ONE builder/shape but reports no counts since phase 90 (renders bare); phase-190's "the other ~72 rows" → "the other 83 rows of the panel's 86-row catalogue" (mechanically: 86 CATALOG rows, 84 overridable). `N remaining` already in the sync paragraph; task-01's `(n/m)` examples gone. AGENTS.md verified consistent (no clause names the running count part → unchanged per the phase's "do not add if absent"). Mechanically re-verified: all 86 `BOR_*` settings documented + defaults match, `.env.example` 88 vars covered, 0 broken README anchors, all cited file paths exist, 17 palette colors, migrations head `0030`, pages/shell routes, ports 8123/8901, E2E port allocations (new suite's 8211/8914 unique).
|
||||||
|
- **Notable:** task 02's documented deviation stands — `files_done` is the importer's 1-based *current* file, so the phase doc's `[1-4]` range is unreachable; count-bearing frames are `1 … N−1` (pinned). Pre-existing, untouched: `E2E_APP_PORT_TOOLBUDGET`/`SAVEDOC` both 8129 and `SYNCUP`/`SYCNCEL` both 8133 — harmless under the one-suite-at-a-time rule.
|
||||||
|
- **Next pending phase:** none — `193_sync_remaining_count` is the last entry in `.agents/phases/todo/`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
........................................................................ [ 1%]
|
||||||
|
........................................................................ [ 3%]
|
||||||
|
........................................................................ [ 4%]
|
||||||
|
........................................................................ [ 6%]
|
||||||
|
........................................................................ [ 7%]
|
||||||
|
........................................................................ [ 9%]
|
||||||
|
........................................................................ [ 10%]
|
||||||
|
........................................................................ [ 12%]
|
||||||
|
........................................................................ [ 13%]
|
||||||
|
........................................................................ [ 15%]
|
||||||
|
........................................................................ [ 16%]
|
||||||
|
........................................................................ [ 18%]
|
||||||
|
........................................................................ [ 19%]
|
||||||
|
........................................................................ [ 21%]
|
||||||
|
........................................................................ [ 22%]
|
||||||
|
........................................................................ [ 24%]
|
||||||
|
........................................................................ [ 25%]
|
||||||
|
........................................................................ [ 27%]
|
||||||
|
........................................................................ [ 28%]
|
||||||
|
........................................................................ [ 30%]
|
||||||
|
........................................................................ [ 31%]
|
||||||
|
........................................................................ [ 33%]
|
||||||
|
........................................................................ [ 34%]
|
||||||
|
........................................................................ [ 36%]
|
||||||
|
........................................................................ [ 37%]
|
||||||
|
........................................................................ [ 39%]
|
||||||
|
........................................................................ [ 40%]
|
||||||
|
........................................................................ [ 42%]
|
||||||
|
........................................................................ [ 43%]
|
||||||
|
........................................................................ [ 45%]
|
||||||
|
........................................................................ [ 46%]
|
||||||
|
........................................................................ [ 48%]
|
||||||
|
........................................................................ [ 49%]
|
||||||
|
........................................................................ [ 51%]
|
||||||
|
........................................................................ [ 52%]
|
||||||
|
........................................................................ [ 54%]
|
||||||
|
........................................................................ [ 55%]
|
||||||
|
........................................................................ [ 57%]
|
||||||
|
........................................................................ [ 58%]
|
||||||
|
........................................................................ [ 60%]
|
||||||
|
........................................................................ [ 61%]
|
||||||
|
........................................................................ [ 63%]
|
||||||
|
........................................................................ [ 64%]
|
||||||
|
........................................................................ [ 66%]
|
||||||
|
........................................................................ [ 67%]
|
||||||
|
........................................................................ [ 69%]
|
||||||
|
........................................................................ [ 70%]
|
||||||
|
........................................................................ [ 72%]
|
||||||
|
........................................................................ [ 73%]
|
||||||
|
........................................................................ [ 75%]
|
||||||
|
........................................................................ [ 76%]
|
||||||
|
........................................................................ [ 78%]
|
||||||
|
........................................................................ [ 79%]
|
||||||
|
........................................................................ [ 81%]
|
||||||
|
........................................................................ [ 82%]
|
||||||
|
........................................................................ [ 84%]
|
||||||
|
........................................................................ [ 85%]
|
||||||
|
........................................................................ [ 87%]
|
||||||
|
........................................................................ [ 88%]
|
||||||
|
........................................................................ [ 90%]
|
||||||
|
........................................................................ [ 91%]
|
||||||
|
........................................................................ [ 93%]
|
||||||
|
........................................................................ [ 94%]
|
||||||
|
........................................................................ [ 96%]
|
||||||
|
........................................................................ [ 97%]
|
||||||
|
........................................................................ [ 99%]
|
||||||
|
........................................ [100%]
|
||||||
|
=============================== warnings summary ===============================
|
||||||
|
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||||
|
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||||
|
from starlette.testclient import TestClient as TestClient # noqa
|
||||||
|
|
||||||
|
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||||
|
================================ tests coverage ================================
|
||||||
|
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||||
|
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
--------------------------------------------------
|
||||||
|
app/__init__.py 1 0 100%
|
||||||
|
app/api/__init__.py 0 0 100%
|
||||||
|
app/api/admin_settings.py 114 0 100%
|
||||||
|
app/api/auth.py 52 0 100%
|
||||||
|
app/api/chat.py 301 1 99%
|
||||||
|
app/api/chat_images.py 52 0 100%
|
||||||
|
app/api/chats.py 111 0 100%
|
||||||
|
app/api/config.py 13 0 100%
|
||||||
|
app/api/doc_drafts.py 99 0 100%
|
||||||
|
app/api/docs.py 239 1 99%
|
||||||
|
app/api/git_sources.py 259 0 100%
|
||||||
|
app/api/health.py 10 0 100%
|
||||||
|
app/api/source_labels.py 52 0 100%
|
||||||
|
app/api/steering.py 42 0 100%
|
||||||
|
app/api/suggestions.py 33 0 100%
|
||||||
|
app/api/sync.py 231 0 100%
|
||||||
|
app/api/tokens.py 40 0 100%
|
||||||
|
app/api/ui_settings.py 39 0 100%
|
||||||
|
app/config.py 332 0 100%
|
||||||
|
app/core/__init__.py 0 0 100%
|
||||||
|
app/core/auth.py 45 0 100%
|
||||||
|
app/core/caching.py 124 0 100%
|
||||||
|
app/core/compression.py 59 0 100%
|
||||||
|
app/core/debugging.py 29 2 93%
|
||||||
|
app/core/docs_push.py 43 0 100%
|
||||||
|
app/core/errors.py 5 0 100%
|
||||||
|
app/core/http_cache.py 22 0 100%
|
||||||
|
app/core/logging.py 13 0 100%
|
||||||
|
app/core/memory.py 32 0 100%
|
||||||
|
app/core/rate_limit.py 44 0 100%
|
||||||
|
app/core/response_budget.py 63 0 100%
|
||||||
|
app/core/security_headers.py 20 0 100%
|
||||||
|
app/core/settings_catalog.py 18 0 100%
|
||||||
|
app/core/settings_migrate.py 32 0 100%
|
||||||
|
app/core/settings_overlay.py 117 0 100%
|
||||||
|
app/core/theming.py 45 0 100%
|
||||||
|
app/core/tokens.py 44 0 100%
|
||||||
|
app/db.py 22 0 100%
|
||||||
|
app/main.py 107 0 100%
|
||||||
|
app/models.py 148 0 100%
|
||||||
|
app/rag/__init__.py 0 0 100%
|
||||||
|
app/rag/agent.py 501 1 99%
|
||||||
|
app/rag/archive_upload.py 134 0 100%
|
||||||
|
app/rag/chunker.py 220 4 98%
|
||||||
|
app/rag/doc_dates.py 18 0 100%
|
||||||
|
app/rag/folder_summaries.py 134 0 100%
|
||||||
|
app/rag/git_sources.py 77 0 100%
|
||||||
|
app/rag/importer.py 865 3 99%
|
||||||
|
app/rag/llm.py 253 1 99%
|
||||||
|
app/rag/md_images.py 63 0 100%
|
||||||
|
app/rag/overview.py 82 0 100%
|
||||||
|
app/rag/pdf.py 192 0 100%
|
||||||
|
app/rag/prompts.py 178 0 100%
|
||||||
|
app/rag/retriever.py 270 3 99%
|
||||||
|
app/rag/scaffolding.py 55 0 100%
|
||||||
|
app/rag/source_labels.py 87 0 100%
|
||||||
|
app/rag/source_removal.py 41 0 100%
|
||||||
|
app/rag/sources_meta.py 16 0 100%
|
||||||
|
app/rag/suggestions.py 27 0 100%
|
||||||
|
app/rag/summarizer.py 58 0 100%
|
||||||
|
app/rag/svg_text.py 111 0 100%
|
||||||
|
app/rag/verbatim.py 160 0 100%
|
||||||
|
app/schemas.py 587 0 100%
|
||||||
|
--------------------------------------------------
|
||||||
|
TOTAL 7181 16 99%
|
||||||
|
coverage gate: app/ 99% (>90%) OK
|
||||||
|
All checks passed!
|
||||||
|
0 errors, 0 warnings, 0 informations
|
||||||
|
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
|
||||||
|
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||||
|
|
||||||
|
validation OK
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
**Task 01 complete — `fmtSyncLabel` count part is now `N remaining` (D1).**
|
||||||
|
|
||||||
|
**Implemented**
|
||||||
|
- `frontend/assets/sources.js` — `fmtSyncLabel` count: `total > 0 && total - done > 0 ? `${total - done} remaining` : ""` (pre-walk `total > 0` guard kept, hidden at `N == 0`, plain integer, upload shares via the one builder); `fmtSyncLabel` + `fmtSyncPhaseLabel` docstrings updated to the count-down shape (lineage kept; summaries `(n/m)`, `· chunk n/m`, `· image n/m`, terminal `counts_line`, `setSyncLabel`/`#sync-progress` and the server/payload untouched).
|
||||||
|
- `tests/unit/test_frontend_sync_upload.py` — new marker pins `test_fmt_sync_label_count_part_is_the_n_remaining_part` (exact expression, both guards, no-separator, docstring clauses incl. the hidden-at-final-file edge + upload sharing) and `test_remaining_part_leaves_the_other_parts_byte_identical` (chunk/image/summaries builders + `fmtSyncResult` unchanged); retargeted the `(2/3)` contract/harness pins to `1 remaining`; node harness gained the `import-final` row (count part behaviorally hidden at N == 0) wired into the chunk/image/box/Cancel loops; module docstring + `_doc_above` helper added.
|
||||||
|
- `README.md` — sync running-label description: "Syncing… <file> (n/m)" → "Syncing… <file> N remaining" with the N = total − done / hidden-at-final-file / upload-sharing clause. `AGENTS.md`: verified no clause names the count part — no change (per phase doc note).
|
||||||
|
|
||||||
|
**Gates (all green)**
|
||||||
|
- `uv run pytest tests/unit/test_frontend_sync_upload.py` → pass (incl. node harness)
|
||||||
|
- `uv run pytest --cov=app --cov-report=term-missing` → **4792 passed**, `app/` coverage **99%** (>90%)
|
||||||
|
- `uv run ruff check .` → clean; `uv run pyright` → clean
|
||||||
|
|
||||||
|
**Decisions / deviations**
|
||||||
|
- E2E running-label count pins (`test_sync_label_mobile.py`, `test_sync_upload_progress.py`, `test_sync_chunk_progress.py`, `test_sync_image_progress.py`, `test_sync_progress_box.py`) are deliberately left for **task 02** ("the pinned suites move"), per phase 193 D2 — the project's default suite (`testpaths`: unit+integration) is 100% green.
|
||||||
|
|
||||||
|
**Next pending task:** `193_sync_remaining_count/02_update_pinned_e2e.md`
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
........................................................................ [ 1%]
|
||||||
|
........................................................................ [ 3%]
|
||||||
|
........................................................................ [ 4%]
|
||||||
|
........................................................................ [ 6%]
|
||||||
|
........................................................................ [ 7%]
|
||||||
|
........................................................................ [ 9%]
|
||||||
|
........................................................................ [ 10%]
|
||||||
|
........................................................................ [ 12%]
|
||||||
|
........................................................................ [ 13%]
|
||||||
|
........................................................................ [ 15%]
|
||||||
|
........................................................................ [ 16%]
|
||||||
|
........................................................................ [ 18%]
|
||||||
|
........................................................................ [ 19%]
|
||||||
|
........................................................................ [ 21%]
|
||||||
|
........................................................................ [ 22%]
|
||||||
|
........................................................................ [ 24%]
|
||||||
|
........................................................................ [ 25%]
|
||||||
|
........................................................................ [ 27%]
|
||||||
|
........................................................................ [ 28%]
|
||||||
|
........................................................................ [ 30%]
|
||||||
|
........................................................................ [ 31%]
|
||||||
|
........................................................................ [ 33%]
|
||||||
|
........................................................................ [ 34%]
|
||||||
|
........................................................................ [ 36%]
|
||||||
|
........................................................................ [ 37%]
|
||||||
|
........................................................................ [ 39%]
|
||||||
|
........................................................................ [ 40%]
|
||||||
|
........................................................................ [ 42%]
|
||||||
|
........................................................................ [ 43%]
|
||||||
|
........................................................................ [ 45%]
|
||||||
|
........................................................................ [ 46%]
|
||||||
|
........................................................................ [ 48%]
|
||||||
|
........................................................................ [ 49%]
|
||||||
|
........................................................................ [ 51%]
|
||||||
|
........................................................................ [ 52%]
|
||||||
|
........................................................................ [ 54%]
|
||||||
|
........................................................................ [ 55%]
|
||||||
|
........................................................................ [ 57%]
|
||||||
|
........................................................................ [ 58%]
|
||||||
|
........................................................................ [ 60%]
|
||||||
|
........................................................................ [ 61%]
|
||||||
|
........................................................................ [ 63%]
|
||||||
|
........................................................................ [ 64%]
|
||||||
|
........................................................................ [ 66%]
|
||||||
|
........................................................................ [ 67%]
|
||||||
|
........................................................................ [ 69%]
|
||||||
|
........................................................................ [ 70%]
|
||||||
|
........................................................................ [ 72%]
|
||||||
|
........................................................................ [ 73%]
|
||||||
|
........................................................................ [ 75%]
|
||||||
|
........................................................................ [ 76%]
|
||||||
|
........................................................................ [ 78%]
|
||||||
|
........................................................................ [ 79%]
|
||||||
|
........................................................................ [ 81%]
|
||||||
|
........................................................................ [ 82%]
|
||||||
|
........................................................................ [ 84%]
|
||||||
|
........................................................................ [ 85%]
|
||||||
|
........................................................................ [ 87%]
|
||||||
|
........................................................................ [ 88%]
|
||||||
|
........................................................................ [ 90%]
|
||||||
|
........................................................................ [ 91%]
|
||||||
|
........................................................................ [ 93%]
|
||||||
|
........................................................................ [ 94%]
|
||||||
|
........................................................................ [ 96%]
|
||||||
|
........................................................................ [ 97%]
|
||||||
|
........................................................................ [ 99%]
|
||||||
|
........................................ [100%]
|
||||||
|
=============================== warnings summary ===============================
|
||||||
|
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||||
|
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||||
|
from starlette.testclient import TestClient as TestClient # noqa
|
||||||
|
|
||||||
|
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||||
|
================================ tests coverage ================================
|
||||||
|
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||||
|
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
--------------------------------------------------
|
||||||
|
app/__init__.py 1 0 100%
|
||||||
|
app/api/__init__.py 0 0 100%
|
||||||
|
app/api/admin_settings.py 114 0 100%
|
||||||
|
app/api/auth.py 52 0 100%
|
||||||
|
app/api/chat.py 301 1 99%
|
||||||
|
app/api/chat_images.py 52 0 100%
|
||||||
|
app/api/chats.py 111 0 100%
|
||||||
|
app/api/config.py 13 0 100%
|
||||||
|
app/api/doc_drafts.py 99 0 100%
|
||||||
|
app/api/docs.py 239 1 99%
|
||||||
|
app/api/git_sources.py 259 0 100%
|
||||||
|
app/api/health.py 10 0 100%
|
||||||
|
app/api/source_labels.py 52 0 100%
|
||||||
|
app/api/steering.py 42 0 100%
|
||||||
|
app/api/suggestions.py 33 0 100%
|
||||||
|
app/api/sync.py 231 0 100%
|
||||||
|
app/api/tokens.py 40 0 100%
|
||||||
|
app/api/ui_settings.py 39 0 100%
|
||||||
|
app/config.py 332 0 100%
|
||||||
|
app/core/__init__.py 0 0 100%
|
||||||
|
app/core/auth.py 45 0 100%
|
||||||
|
app/core/caching.py 124 0 100%
|
||||||
|
app/core/compression.py 59 0 100%
|
||||||
|
app/core/debugging.py 29 2 93%
|
||||||
|
app/core/docs_push.py 43 0 100%
|
||||||
|
app/core/errors.py 5 0 100%
|
||||||
|
app/core/http_cache.py 22 0 100%
|
||||||
|
app/core/logging.py 13 0 100%
|
||||||
|
app/core/memory.py 32 0 100%
|
||||||
|
app/core/rate_limit.py 44 0 100%
|
||||||
|
app/core/response_budget.py 63 0 100%
|
||||||
|
app/core/security_headers.py 20 0 100%
|
||||||
|
app/core/settings_catalog.py 18 0 100%
|
||||||
|
app/core/settings_migrate.py 32 0 100%
|
||||||
|
app/core/settings_overlay.py 117 0 100%
|
||||||
|
app/core/theming.py 45 0 100%
|
||||||
|
app/core/tokens.py 44 0 100%
|
||||||
|
app/db.py 22 0 100%
|
||||||
|
app/main.py 107 0 100%
|
||||||
|
app/models.py 148 0 100%
|
||||||
|
app/rag/__init__.py 0 0 100%
|
||||||
|
app/rag/agent.py 501 1 99%
|
||||||
|
app/rag/archive_upload.py 134 0 100%
|
||||||
|
app/rag/chunker.py 220 4 98%
|
||||||
|
app/rag/doc_dates.py 18 0 100%
|
||||||
|
app/rag/folder_summaries.py 134 0 100%
|
||||||
|
app/rag/git_sources.py 77 0 100%
|
||||||
|
app/rag/importer.py 865 3 99%
|
||||||
|
app/rag/llm.py 253 1 99%
|
||||||
|
app/rag/md_images.py 63 0 100%
|
||||||
|
app/rag/overview.py 82 0 100%
|
||||||
|
app/rag/pdf.py 192 0 100%
|
||||||
|
app/rag/prompts.py 178 0 100%
|
||||||
|
app/rag/retriever.py 270 3 99%
|
||||||
|
app/rag/scaffolding.py 55 0 100%
|
||||||
|
app/rag/source_labels.py 87 0 100%
|
||||||
|
app/rag/source_removal.py 41 0 100%
|
||||||
|
app/rag/sources_meta.py 16 0 100%
|
||||||
|
app/rag/suggestions.py 27 0 100%
|
||||||
|
app/rag/summarizer.py 58 0 100%
|
||||||
|
app/rag/svg_text.py 111 0 100%
|
||||||
|
app/rag/verbatim.py 160 0 100%
|
||||||
|
app/schemas.py 587 0 100%
|
||||||
|
--------------------------------------------------
|
||||||
|
TOTAL 7181 16 99%
|
||||||
|
coverage gate: app/ 99% (>90%) OK
|
||||||
|
All checks passed!
|
||||||
|
0 errors, 0 warnings, 0 informations
|
||||||
|
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
|
||||||
|
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||||
|
|
||||||
|
validation OK
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
**Task 02 complete — every running-label count pin now asserts `N remaining`.**
|
||||||
|
|
||||||
|
**Implemented (test-only + doc hygiene)**
|
||||||
|
- `tests/e2e/test_sync_label_mobile.py`: `IMPORT_COUNT_RE`/`IMPORT_FULL_RE` + both in-browser JS regexes → `[1-3] remaining` (4-file fixture; `files_done` is the importer's **1-based current file**, so N = 4−done ∈ {3,2,1} and the last file renders no part — the phase doc's "`done = i − 1`" arithmetic was wrong); `IMPORT_MAIN_RE` untouched.
|
||||||
|
- `tests/e2e/test_sync_upload_progress.py`: the 4 box/title count regexes → `(?: [0-9]+ remaining)?` (optional — see deviations).
|
||||||
|
- `tests/e2e/test_sync_chunk_progress.py`: JS `count.textContent`, `countText`, the composed A4 string → `1 remaining` (big.md = file 1 of 2).
|
||||||
|
- `tests/e2e/test_sync_progress_box.py`: the 3 JS waits + 2 Python asserts → `2 remaining` (long file = file 1 of 3).
|
||||||
|
- `tests/e2e/test_sync_image_progress.py`: the single-file fixture's `(1/1)` pin became the **hidden-at-final-file** arm (`no running label carries " remaining"` + the live-file label still pinned).
|
||||||
|
- Phase-193 lineage sentence added to each retargeted module docstring; stale `(n/m)` prose fixed in `test_archive_upload_sources.py`, `test_sync_status_freshness.py`, `test_upload_no_scan.py`, `tests/unit/test_sync_button.py`, `tests/unit/test_frontend_sync_upload.py`.
|
||||||
|
- **Untouched and green**: the `· chunk n/m` / `· image n/m` parts, the summaries `(n/3)` folder position, and every `counts_line` terminal pin (`git_freeze`, `session_reset`, `fast_path_deferred_content`).
|
||||||
|
- Pin list closed: the remaining hits of the phase's grep are bare `"Syncing…"` asserts and the owner's quoted reports.
|
||||||
|
|
||||||
|
**Gates**
|
||||||
|
- `uv run pytest` → **4792 passed**; `uv run pytest --cov=app --cov-report=term-missing` → **99%** (>90%).
|
||||||
|
- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors.
|
||||||
|
- E2E (`--no-cov`, isolated): `test_sync_label_mobile.py + test_sync_upload_progress.py` 8 passed (the task's combined command); `test_sync_progress_box.py` 8; `test_sync_chunk_progress.py + test_sync_image_progress.py` 4; `+ test_sync_summary_visibility.py` 7; `test_sync_git_freeze.py` 2; `test_archive_upload_sources.py + test_sync_session_reset.py` 9; `test_fast_path_deferred_content.py + test_sync_button.py + test_sync_peak_memory_visible.py` 11; `test_sync_status_freshness.py + test_upload_no_scan.py` in the summary batch.
|
||||||
|
|
||||||
|
**Docs**: README's sync running-label paragraph — removed the stray `— and —` the task-01 edit left and re-flowed the wrap; AGENTS.md has no count-part clause (no doc impact for it; task 03 re-verifies).
|
||||||
|
|
||||||
|
**Deviations**: (1) the phase's D2 range `[1-4] remaining` is impossible — `files_done` includes the in-flight file, so the count-bearing frames are `[1-3]` (kept **mandatory** where the pin's whole point is "the count is in the text"); (2) the upload suite's 25-file box/title pins make the part **optional**, because its 2 s poll can land exactly on the final file where A1 hides the part — the pattern still rejects a pre-phase `(n/m)` tail.
|
||||||
|
|
||||||
|
**Next pending task**: `.agents/phases/todo/193_sync_remaining_count/03_e2e.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
........................................................................ [ 1%]
|
||||||
|
........................................................................ [ 3%]
|
||||||
|
........................................................................ [ 4%]
|
||||||
|
........................................................................ [ 6%]
|
||||||
|
........................................................................ [ 7%]
|
||||||
|
........................................................................ [ 9%]
|
||||||
|
........................................................................ [ 10%]
|
||||||
|
........................................................................ [ 12%]
|
||||||
|
........................................................................ [ 13%]
|
||||||
|
........................................................................ [ 15%]
|
||||||
|
........................................................................ [ 16%]
|
||||||
|
........................................................................ [ 18%]
|
||||||
|
........................................................................ [ 19%]
|
||||||
|
........................................................................ [ 21%]
|
||||||
|
........................................................................ [ 22%]
|
||||||
|
........................................................................ [ 24%]
|
||||||
|
........................................................................ [ 25%]
|
||||||
|
........................................................................ [ 27%]
|
||||||
|
........................................................................ [ 28%]
|
||||||
|
........................................................................ [ 30%]
|
||||||
|
........................................................................ [ 31%]
|
||||||
|
........................................................................ [ 33%]
|
||||||
|
........................................................................ [ 34%]
|
||||||
|
........................................................................ [ 36%]
|
||||||
|
........................................................................ [ 37%]
|
||||||
|
........................................................................ [ 39%]
|
||||||
|
........................................................................ [ 40%]
|
||||||
|
........................................................................ [ 42%]
|
||||||
|
........................................................................ [ 43%]
|
||||||
|
........................................................................ [ 45%]
|
||||||
|
........................................................................ [ 46%]
|
||||||
|
........................................................................ [ 48%]
|
||||||
|
........................................................................ [ 49%]
|
||||||
|
........................................................................ [ 51%]
|
||||||
|
........................................................................ [ 52%]
|
||||||
|
........................................................................ [ 54%]
|
||||||
|
........................................................................ [ 55%]
|
||||||
|
........................................................................ [ 57%]
|
||||||
|
........................................................................ [ 58%]
|
||||||
|
........................................................................ [ 60%]
|
||||||
|
........................................................................ [ 61%]
|
||||||
|
........................................................................ [ 63%]
|
||||||
|
........................................................................ [ 64%]
|
||||||
|
........................................................................ [ 66%]
|
||||||
|
........................................................................ [ 67%]
|
||||||
|
........................................................................ [ 69%]
|
||||||
|
........................................................................ [ 70%]
|
||||||
|
........................................................................ [ 72%]
|
||||||
|
........................................................................ [ 73%]
|
||||||
|
........................................................................ [ 75%]
|
||||||
|
........................................................................ [ 76%]
|
||||||
|
........................................................................ [ 78%]
|
||||||
|
........................................................................ [ 79%]
|
||||||
|
........................................................................ [ 81%]
|
||||||
|
........................................................................ [ 82%]
|
||||||
|
........................................................................ [ 84%]
|
||||||
|
........................................................................ [ 85%]
|
||||||
|
........................................................................ [ 87%]
|
||||||
|
........................................................................ [ 88%]
|
||||||
|
........................................................................ [ 90%]
|
||||||
|
........................................................................ [ 91%]
|
||||||
|
........................................................................ [ 93%]
|
||||||
|
........................................................................ [ 94%]
|
||||||
|
........................................................................ [ 96%]
|
||||||
|
........................................................................ [ 97%]
|
||||||
|
........................................................................ [ 99%]
|
||||||
|
........................................ [100%]
|
||||||
|
=============================== warnings summary ===============================
|
||||||
|
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||||
|
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||||
|
from starlette.testclient import TestClient as TestClient # noqa
|
||||||
|
|
||||||
|
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||||
|
================================ tests coverage ================================
|
||||||
|
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||||
|
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
--------------------------------------------------
|
||||||
|
app/__init__.py 1 0 100%
|
||||||
|
app/api/__init__.py 0 0 100%
|
||||||
|
app/api/admin_settings.py 114 0 100%
|
||||||
|
app/api/auth.py 52 0 100%
|
||||||
|
app/api/chat.py 301 1 99%
|
||||||
|
app/api/chat_images.py 52 0 100%
|
||||||
|
app/api/chats.py 111 0 100%
|
||||||
|
app/api/config.py 13 0 100%
|
||||||
|
app/api/doc_drafts.py 99 0 100%
|
||||||
|
app/api/docs.py 239 1 99%
|
||||||
|
app/api/git_sources.py 259 0 100%
|
||||||
|
app/api/health.py 10 0 100%
|
||||||
|
app/api/source_labels.py 52 0 100%
|
||||||
|
app/api/steering.py 42 0 100%
|
||||||
|
app/api/suggestions.py 33 0 100%
|
||||||
|
app/api/sync.py 231 0 100%
|
||||||
|
app/api/tokens.py 40 0 100%
|
||||||
|
app/api/ui_settings.py 39 0 100%
|
||||||
|
app/config.py 332 0 100%
|
||||||
|
app/core/__init__.py 0 0 100%
|
||||||
|
app/core/auth.py 45 0 100%
|
||||||
|
app/core/caching.py 124 0 100%
|
||||||
|
app/core/compression.py 59 0 100%
|
||||||
|
app/core/debugging.py 29 2 93%
|
||||||
|
app/core/docs_push.py 43 0 100%
|
||||||
|
app/core/errors.py 5 0 100%
|
||||||
|
app/core/http_cache.py 22 0 100%
|
||||||
|
app/core/logging.py 13 0 100%
|
||||||
|
app/core/memory.py 32 0 100%
|
||||||
|
app/core/rate_limit.py 44 0 100%
|
||||||
|
app/core/response_budget.py 63 0 100%
|
||||||
|
app/core/security_headers.py 20 0 100%
|
||||||
|
app/core/settings_catalog.py 18 0 100%
|
||||||
|
app/core/settings_migrate.py 32 0 100%
|
||||||
|
app/core/settings_overlay.py 117 0 100%
|
||||||
|
app/core/theming.py 45 0 100%
|
||||||
|
app/core/tokens.py 44 0 100%
|
||||||
|
app/db.py 22 0 100%
|
||||||
|
app/main.py 107 0 100%
|
||||||
|
app/models.py 148 0 100%
|
||||||
|
app/rag/__init__.py 0 0 100%
|
||||||
|
app/rag/agent.py 501 1 99%
|
||||||
|
app/rag/archive_upload.py 134 0 100%
|
||||||
|
app/rag/chunker.py 220 4 98%
|
||||||
|
app/rag/doc_dates.py 18 0 100%
|
||||||
|
app/rag/folder_summaries.py 134 0 100%
|
||||||
|
app/rag/git_sources.py 77 0 100%
|
||||||
|
app/rag/importer.py 865 3 99%
|
||||||
|
app/rag/llm.py 253 1 99%
|
||||||
|
app/rag/md_images.py 63 0 100%
|
||||||
|
app/rag/overview.py 82 0 100%
|
||||||
|
app/rag/pdf.py 192 0 100%
|
||||||
|
app/rag/prompts.py 178 0 100%
|
||||||
|
app/rag/retriever.py 270 3 99%
|
||||||
|
app/rag/scaffolding.py 55 0 100%
|
||||||
|
app/rag/source_labels.py 87 0 100%
|
||||||
|
app/rag/source_removal.py 41 0 100%
|
||||||
|
app/rag/sources_meta.py 16 0 100%
|
||||||
|
app/rag/suggestions.py 27 0 100%
|
||||||
|
app/rag/summarizer.py 58 0 100%
|
||||||
|
app/rag/svg_text.py 111 0 100%
|
||||||
|
app/rag/verbatim.py 160 0 100%
|
||||||
|
app/schemas.py 587 0 100%
|
||||||
|
--------------------------------------------------
|
||||||
|
TOTAL 7181 16 99%
|
||||||
|
coverage gate: app/ 99% (>90%) OK
|
||||||
|
All checks passed!
|
||||||
|
0 errors, 0 warnings, 0 informations
|
||||||
|
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
|
||||||
|
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||||
|
|
||||||
|
validation OK
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
**Task 03 complete — phase 193's own E2E suite is green in isolation; all gates green.**
|
||||||
|
|
||||||
|
**Implemented**
|
||||||
|
- `tests/e2e/test_sync_remaining_count.py` (NEW): a real `file://` git fixture (5 docs, first + last multi-chunk) + the slow-LLM proxy, with a MutationObserver that records EVERY value the one label writer lands (`#sync-progress-text`, button `title`/`aria-label`, `.sync-label-count` visibility). Arms: **(a)** the count-down — grammar `^Syncing… <file> [1-4] remaining(?: · chunk n/m)?$`, rendered `N`s strictly decreasing 4→3→2→1, never 0/≥total, chunk part coexisting (its `m` = the file's real `chunk_document` count), NO count part once the final file is named and never returning, the overview + summaries `(n/m)` phases intact, the terminal `counts_line("5 added")` byte-identical, plus the cross-layer invariant that every rendered `N` equals `files_total − files_done` of the untouched payload; **(c)** the pre-walk `phase:null`/`files_total:0` → bare `Syncing…` and the hidden-at-final edge, pinned on served bodies of the endpoint's unchanged key set; **(b)** the upload arm through the same builder (`Importing <file> N remaining`, hidden at its final file).
|
||||||
|
- Docs pass: the stale `(n/m)` running-label examples became `N remaining` in `frontend/assets/sources.js` (5 header/comment sites), `frontend/assets/styles.css`, `frontend/index.html`, `frontend/assets/git-sources.js`. `README.md` already carried the phase-193 label (task 01); `AGENTS.md` has no clause naming the running count part — unchanged.
|
||||||
|
|
||||||
|
**Gates (final state)** `uv run pytest --cov=app --cov-report=term-missing` → exit 0, `app/` **99%**; `tests/e2e/test_sync_remaining_count.py -v --no-cov` → 3 passed in isolation (twice); regression `test_sync_label_mobile + test_sync_upload_progress + test_sync_chunk_progress + test_sync_summary_visibility` → 13 passed; `ruff check .` clean; `pyright` 0 errors.
|
||||||
|
|
||||||
|
**Decisions / deviations**
|
||||||
|
1. A2's upload arm is not observable live: since phase 90 the upload status answers `null/0/0` for the whole run (asserted against the live endpoint inside that test), so the shared *shape* is pinned through a served status body (the house `page.route` pattern) — the client's `total > 0` / `total − done > 0` guards are what is under test, the server stays untouched.
|
||||||
|
2. The upload frame can never carry `· chunk n/m` (that branch receives no status object — phase 129/152), so the coexistence pin sits in the sync arms and the upload test asserts the frame stays chunk-free.
|
||||||
|
3. Count-bearing frames are `1 … N−1` (`files_done` is the 1-based in-flight file) — task 02's finding, kept here.
|
||||||
|
4. No git commands run and the phase directory was not moved, per the harness instruction — commit + `todo/ → complete/` left to the harness.
|
||||||
|
|
||||||
|
**Next task:** none pending in `193_sync_remaining_count` (03 is its last).
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
........................................................................ [ 1%]
|
||||||
|
........................................................................ [ 3%]
|
||||||
|
........................................................................ [ 4%]
|
||||||
|
........................................................................ [ 6%]
|
||||||
|
........................................................................ [ 7%]
|
||||||
|
........................................................................ [ 9%]
|
||||||
|
........................................................................ [ 10%]
|
||||||
|
........................................................................ [ 12%]
|
||||||
|
........................................................................ [ 13%]
|
||||||
|
........................................................................ [ 15%]
|
||||||
|
........................................................................ [ 16%]
|
||||||
|
........................................................................ [ 18%]
|
||||||
|
........................................................................ [ 19%]
|
||||||
|
........................................................................ [ 21%]
|
||||||
|
........................................................................ [ 22%]
|
||||||
|
........................................................................ [ 24%]
|
||||||
|
........................................................................ [ 25%]
|
||||||
|
........................................................................ [ 27%]
|
||||||
|
........................................................................ [ 28%]
|
||||||
|
........................................................................ [ 30%]
|
||||||
|
........................................................................ [ 31%]
|
||||||
|
........................................................................ [ 33%]
|
||||||
|
........................................................................ [ 34%]
|
||||||
|
........................................................................ [ 36%]
|
||||||
|
........................................................................ [ 37%]
|
||||||
|
........................................................................ [ 39%]
|
||||||
|
........................................................................ [ 40%]
|
||||||
|
........................................................................ [ 42%]
|
||||||
|
........................................................................ [ 43%]
|
||||||
|
........................................................................ [ 45%]
|
||||||
|
........................................................................ [ 46%]
|
||||||
|
........................................................................ [ 48%]
|
||||||
|
........................................................................ [ 49%]
|
||||||
|
........................................................................ [ 51%]
|
||||||
|
........................................................................ [ 52%]
|
||||||
|
........................................................................ [ 54%]
|
||||||
|
........................................................................ [ 55%]
|
||||||
|
........................................................................ [ 57%]
|
||||||
|
........................................................................ [ 58%]
|
||||||
|
........................................................................ [ 60%]
|
||||||
|
........................................................................ [ 61%]
|
||||||
|
........................................................................ [ 63%]
|
||||||
|
........................................................................ [ 64%]
|
||||||
|
........................................................................ [ 66%]
|
||||||
|
........................................................................ [ 67%]
|
||||||
|
........................................................................ [ 69%]
|
||||||
|
........................................................................ [ 70%]
|
||||||
|
........................................................................ [ 72%]
|
||||||
|
........................................................................ [ 73%]
|
||||||
|
........................................................................ [ 75%]
|
||||||
|
........................................................................ [ 76%]
|
||||||
|
........................................................................ [ 78%]
|
||||||
|
........................................................................ [ 79%]
|
||||||
|
........................................................................ [ 81%]
|
||||||
|
........................................................................ [ 82%]
|
||||||
|
........................................................................ [ 84%]
|
||||||
|
........................................................................ [ 85%]
|
||||||
|
........................................................................ [ 87%]
|
||||||
|
........................................................................ [ 88%]
|
||||||
|
........................................................................ [ 90%]
|
||||||
|
........................................................................ [ 91%]
|
||||||
|
........................................................................ [ 93%]
|
||||||
|
........................................................................ [ 94%]
|
||||||
|
........................................................................ [ 96%]
|
||||||
|
........................................................................ [ 97%]
|
||||||
|
........................................................................ [ 99%]
|
||||||
|
........................................ [100%]
|
||||||
|
=============================== warnings summary ===============================
|
||||||
|
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||||
|
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||||
|
from starlette.testclient import TestClient as TestClient # noqa
|
||||||
|
|
||||||
|
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||||
|
================================ tests coverage ================================
|
||||||
|
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||||
|
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
--------------------------------------------------
|
||||||
|
app/__init__.py 1 0 100%
|
||||||
|
app/api/__init__.py 0 0 100%
|
||||||
|
app/api/admin_settings.py 114 0 100%
|
||||||
|
app/api/auth.py 52 0 100%
|
||||||
|
app/api/chat.py 301 1 99%
|
||||||
|
app/api/chat_images.py 52 0 100%
|
||||||
|
app/api/chats.py 111 0 100%
|
||||||
|
app/api/config.py 13 0 100%
|
||||||
|
app/api/doc_drafts.py 99 0 100%
|
||||||
|
app/api/docs.py 239 1 99%
|
||||||
|
app/api/git_sources.py 259 0 100%
|
||||||
|
app/api/health.py 10 0 100%
|
||||||
|
app/api/source_labels.py 52 0 100%
|
||||||
|
app/api/steering.py 42 0 100%
|
||||||
|
app/api/suggestions.py 33 0 100%
|
||||||
|
app/api/sync.py 231 0 100%
|
||||||
|
app/api/tokens.py 40 0 100%
|
||||||
|
app/api/ui_settings.py 39 0 100%
|
||||||
|
app/config.py 332 0 100%
|
||||||
|
app/core/__init__.py 0 0 100%
|
||||||
|
app/core/auth.py 45 0 100%
|
||||||
|
app/core/caching.py 124 0 100%
|
||||||
|
app/core/compression.py 59 0 100%
|
||||||
|
app/core/debugging.py 29 2 93%
|
||||||
|
app/core/docs_push.py 43 0 100%
|
||||||
|
app/core/errors.py 5 0 100%
|
||||||
|
app/core/http_cache.py 22 0 100%
|
||||||
|
app/core/logging.py 13 0 100%
|
||||||
|
app/core/memory.py 32 0 100%
|
||||||
|
app/core/rate_limit.py 44 0 100%
|
||||||
|
app/core/response_budget.py 63 0 100%
|
||||||
|
app/core/security_headers.py 20 0 100%
|
||||||
|
app/core/settings_catalog.py 18 0 100%
|
||||||
|
app/core/settings_migrate.py 32 0 100%
|
||||||
|
app/core/settings_overlay.py 117 0 100%
|
||||||
|
app/core/theming.py 45 0 100%
|
||||||
|
app/core/tokens.py 44 0 100%
|
||||||
|
app/db.py 22 0 100%
|
||||||
|
app/main.py 107 0 100%
|
||||||
|
app/models.py 148 0 100%
|
||||||
|
app/rag/__init__.py 0 0 100%
|
||||||
|
app/rag/agent.py 501 1 99%
|
||||||
|
app/rag/archive_upload.py 134 0 100%
|
||||||
|
app/rag/chunker.py 220 4 98%
|
||||||
|
app/rag/doc_dates.py 18 0 100%
|
||||||
|
app/rag/folder_summaries.py 134 0 100%
|
||||||
|
app/rag/git_sources.py 77 0 100%
|
||||||
|
app/rag/importer.py 865 3 99%
|
||||||
|
app/rag/llm.py 253 1 99%
|
||||||
|
app/rag/md_images.py 63 0 100%
|
||||||
|
app/rag/overview.py 82 0 100%
|
||||||
|
app/rag/pdf.py 192 0 100%
|
||||||
|
app/rag/prompts.py 178 0 100%
|
||||||
|
app/rag/retriever.py 270 3 99%
|
||||||
|
app/rag/scaffolding.py 55 0 100%
|
||||||
|
app/rag/source_labels.py 87 0 100%
|
||||||
|
app/rag/source_removal.py 41 0 100%
|
||||||
|
app/rag/sources_meta.py 16 0 100%
|
||||||
|
app/rag/suggestions.py 27 0 100%
|
||||||
|
app/rag/summarizer.py 58 0 100%
|
||||||
|
app/rag/svg_text.py 111 0 100%
|
||||||
|
app/rag/verbatim.py 160 0 100%
|
||||||
|
app/schemas.py 587 0 100%
|
||||||
|
--------------------------------------------------
|
||||||
|
TOTAL 7181 16 99%
|
||||||
|
coverage gate: app/ 99% (>90%) OK
|
||||||
|
All checks passed!
|
||||||
|
0 errors, 0 warnings, 0 informations
|
||||||
|
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
|
||||||
|
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
|
||||||
|
|
||||||
|
validation OK
|
||||||
@@ -727,12 +727,17 @@ only) runs the whole source refresh in one click:
|
|||||||
- States: while polling `GET /api/sync/status` every 2 s, the button
|
- States: while polling `GET /api/sync/status` every 2 s, the button
|
||||||
becomes the spinner (icon only — its label parts are hidden while a
|
becomes the spinner (icon only — its label parts are hidden while a
|
||||||
run is in flight, and its accessible name is the full live label),
|
run is in flight, and its accessible name is the full live label),
|
||||||
and the full live detail — "Syncing… <file> (n/m)" during the import
|
and the full live detail — "Syncing… <file> N remaining" during the
|
||||||
phase (the live file, its `(n/m)` file position, and — while a file's
|
import phase (the live file, its `N remaining` file count — N =
|
||||||
embed batch is in flight — the pinned `· chunk n/m` embed position,
|
total − done, shown while N ≥ 1 and hidden at the final file, since
|
||||||
batch granularity: the first chunk of the in-flight batch over the
|
the owner reads the distance to the end better than the position in
|
||||||
file's total content chunks; and — while a PDF's images are being
|
the whole; the upload's `Importing` label shares the ONE builder and
|
||||||
described — the `· image n/m` vision position: the 1-based image
|
the same shape, though since phase 90 that run reports no counts, so
|
||||||
|
today it renders bare; and — while a file's embed batch is in flight
|
||||||
|
— the pinned `· chunk n/m` embed position, batch granularity: the
|
||||||
|
first chunk of the in-flight batch over the file's total content
|
||||||
|
chunks; and — while a PDF's images are being described — the
|
||||||
|
`· image n/m` vision position: the 1-based image
|
||||||
about to be described over the file's total images, ONE continuous
|
about to be described over the file's total images, ONE continuous
|
||||||
counter across the PDF's three vision spans, so a long image pass
|
counter across the PDF's three vision spans, so a long image pass
|
||||||
visibly climbs instead of sitting frozen on the last chunk
|
visibly climbs instead of sitting frozen on the last chunk
|
||||||
@@ -2001,11 +2006,12 @@ to the OPEN document through the ONE shared brand pass (`assets/brand.js`'s
|
|||||||
header brand, the footer line and the chat composer placeholder update on the
|
header brand, the footer line and the chat composer placeholder update on the
|
||||||
click, no reload — including a rename of an app whose old name is already on
|
click, no reload — including a rename of an app whose old name is already on
|
||||||
the page. The re-apply runs only when one of those three keys was involved (the
|
the page. The re-apply runs only when one of those three keys was involved (the
|
||||||
other ~72 rows cost no extra request), never stacks a second fetch while one is
|
other 83 rows of the panel's 86-row catalogue cost no extra request), never
|
||||||
in flight, and a failed one leaves the page exactly as it showed — the save's
|
stacks a second fetch while one is in flight, and a failed one leaves the page
|
||||||
own status or error line stays the feedback. The Theme tab runs the same pass
|
exactly as it showed — the save's own status or error line stays the feedback.
|
||||||
after its Save and its Reset; it no longer edits the strings, so that read is
|
The Theme tab runs the same pass after its Save and its Reset; it no longer
|
||||||
what keeps an open tab honest about a name the panel changed meanwhile.
|
edits the strings, so that read is what keeps an open tab honest about a name
|
||||||
|
the panel changed meanwhile.
|
||||||
The whole contract — the incident regression (a color save no longer shadows a
|
The whole contract — the incident regression (a color save no longer shadows a
|
||||||
later rename) and the live repaint of title / brand / footer / placeholder — is
|
later rename) and the live repaint of title / brand / footer / placeholder — is
|
||||||
pinned in a real browser by `tests/e2e/test_app_name_settings_fix.py` (run in
|
pinned in a real browser by `tests/e2e/test_app_name_settings_fix.py` (run in
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
* disabled, title cleared) plus startUploadPolling(): a 2 s poll
|
* disabled, title cleared) plus startUploadPolling(): a 2 s poll
|
||||||
* of GET /api/git-sources/upload/status that renders the bare
|
* of GET /api/git-sources/upload/status that renders the bare
|
||||||
* "Processing…" label for the WHOLE run (phase 90, A2 — the run
|
* "Processing…" label for the WHOLE run (phase 90, A2 — the run
|
||||||
* is unpack + register only: no file, no "(n/m)" counts, no
|
* is unpack + register only: no file, no "N remaining" count, no
|
||||||
* title) and settles it: success → the ready-for-sync line
|
* title) and settles it: success → the ready-for-sync line
|
||||||
* ("Uploaded <name> (<stamp>) — press Sync sources to import
|
* ("Uploaded <name> (<stamp>) — press Sync sources to import
|
||||||
* it." — the phase-154 completion stamp, fmtUploadStamp off the
|
* it." — the phase-154 completion stamp, fmtUploadStamp off the
|
||||||
@@ -1651,7 +1651,7 @@ export async function mount(root) {
|
|||||||
* title cleared) + startUploadPolling() own it — a 2 s poll of
|
* title cleared) + startUploadPolling() own it — a 2 s poll of
|
||||||
* GET /api/git-sources/upload/status that renders the bare
|
* GET /api/git-sources/upload/status that renders the bare
|
||||||
* "Processing…" label for the WHOLE run (phase 90, A2 — the unpack
|
* "Processing…" label for the WHOLE run (phase 90, A2 — the unpack
|
||||||
* has no file-level progress: no file, no "(n/m)" counts, no title)
|
* has no file-level progress: no file, no "N remaining" count, no title)
|
||||||
* and settles it: success → the ready-for-sync line ("Uploaded
|
* and settles it: success → the ready-for-sync line ("Uploaded
|
||||||
* <name> (<stamp>) — press Sync sources to import it." — the
|
* <name> (<stamp>) — press Sync sources to import it." — the
|
||||||
* phase-154 completion stamp, fmtUploadStamp off the status's
|
* phase-154 completion stamp, fmtUploadStamp off the status's
|
||||||
@@ -1786,7 +1786,7 @@ export async function mount(root) {
|
|||||||
* poll OWNS the button). Each tick fetches
|
* poll OWNS the button). Each tick fetches
|
||||||
* GET /api/git-sources/upload/status: running → the bare
|
* GET /api/git-sources/upload/status: running → the bare
|
||||||
* "Processing…" label for the whole run (phase 90, A2 — the unpack
|
* "Processing…" label for the whole run (phase 90, A2 — the unpack
|
||||||
* has no file-level progress: no file, no "(n/m)" counts, the title
|
* has no file-level progress: no file, no "N remaining" count, the title
|
||||||
* stays clear) + reschedule; success → stop + the ready-for-sync
|
* stays clear) + reschedule; success → stop + the ready-for-sync
|
||||||
* result line + the announcement + the row reload (NO toast — it
|
* result line + the announcement + the row reload (NO toast — it
|
||||||
* fired at the 202, A2); failed → stop + the sanitized server error
|
* fired at the 202, A2); failed → stop + the sanitized server error
|
||||||
|
|||||||
+33
-14
@@ -160,7 +160,8 @@
|
|||||||
* <source/folder> (n/m)" (the folder part omitted while
|
* <source/folder> (n/m)" (the folder part omitted while
|
||||||
* current_summary is null — the phase's first poll), anything else
|
* current_summary is null — the phase's first poll), anything else
|
||||||
* (the null prelude, "import") → today's "Syncing… <file> (n/m)"
|
* (the null prelude, "import") → today's "Syncing… <file> (n/m)"
|
||||||
* byte-identical. The UPLOAD job (its status has no phase) keeps the
|
* byte-identical (the count part's TEXT is `N remaining` — the distance
|
||||||
|
* to the end — since phase 193). The UPLOAD job (its status has no phase) keeps the
|
||||||
* bare label. The untruncated label rides the button title (now set
|
* bare label. The untruncated label rides the button title (now set
|
||||||
* for EVERY running label, not just file labels) + #sync-progress as
|
* for EVERY running label, not just file labels) + #sync-progress as
|
||||||
* before (A4).
|
* before (A4).
|
||||||
@@ -309,7 +310,8 @@
|
|||||||
* Phase 128 (task 01) — the sync label becomes PARTS: the running
|
* Phase 128 (task 01) — the sync label becomes PARTS: the running
|
||||||
* label's string is split into a shrinks-to-fit MAIN part (prefix +
|
* label's string is split into a shrinks-to-fit MAIN part (prefix +
|
||||||
* the file/folder path — the part CSS may ellipsize) and a pinned
|
* the file/folder path — the part CSS may ellipsize) and a pinned
|
||||||
* COUNT part ("(n/m)" — the position, NEVER clipped), so on a phone
|
* COUNT part ("(n/m)" — the position, NEVER clipped — the text is
|
||||||
|
* `N remaining` since phase 193), so on a phone
|
||||||
* the file path ellipsizes to fit while the count stays on-screen
|
* the file path ellipsizes to fit while the count stays on-screen
|
||||||
* (the phase-64 bug: with max-width lifted, the inline-block sized to
|
* (the phase-64 bug: with max-width lifted, the inline-block sized to
|
||||||
* its full content and the count ran off-screen). #sync-label now
|
* its full content and the count ran off-screen). #sync-label now
|
||||||
@@ -332,7 +334,8 @@
|
|||||||
* the initSyncButton re-attach — goes through setSyncLabel(main)
|
* the initSyncButton re-attach — goes through setSyncLabel(main)
|
||||||
* (count hidden). The full-string shapes are byte-identical to the
|
* (count hidden). The full-string shapes are byte-identical to the
|
||||||
* pre-task labels ("Syncing…", "Syncing… <file>",
|
* pre-task labels ("Syncing…", "Syncing… <file>",
|
||||||
* "Syncing… <file> (n/m)", "Importing", "Writing KB overview…",
|
* "Syncing… <file> (n/m)" — `N remaining` since phase 193, "Importing",
|
||||||
|
* "Writing KB overview…",
|
||||||
* "Summarizing folders… <folder> (n/m)"); textContent only — no HTML
|
* "Summarizing folders… <folder> (n/m)"); textContent only — no HTML
|
||||||
* strings (the house rule).
|
* strings (the house rule).
|
||||||
*
|
*
|
||||||
@@ -747,7 +750,7 @@ export async function mount(root) {
|
|||||||
* tree, in order (startSyncPolling):
|
* tree, in order (startSyncPolling):
|
||||||
* 1. sync running → the phase-aware label (Phase 98 task 02, D2)
|
* 1. sync running → the phase-aware label (Phase 98 task 02, D2)
|
||||||
* — the null-phase prelude + "import" →
|
* — the null-phase prelude + "import" →
|
||||||
* "Syncing… <file> (n/m)" — bare "Syncing…"
|
* "Syncing… <file> N remaining" — bare "Syncing…"
|
||||||
* until the import's first file (clone/pull,
|
* until the import's first file (clone/pull,
|
||||||
* A4, byte-identical); "overview" → "Writing KB
|
* A4, byte-identical); "overview" → "Writing KB
|
||||||
* overview…"; "summaries" → "Summarizing
|
* overview…"; "summaries" → "Summarizing
|
||||||
@@ -813,7 +816,8 @@ export async function mount(root) {
|
|||||||
* Elements: #sync-btn (the button), #sync-label (the text —
|
* Elements: #sync-btn (the button), #sync-label (the text —
|
||||||
* Phase 128 (task 01): the PARTS — .sync-label-main, the
|
* Phase 128 (task 01): the PARTS — .sync-label-main, the
|
||||||
* shrinks-to-fit prefix + file/folder, + .sync-label-count, the
|
* shrinks-to-fit prefix + file/folder, + .sync-label-count, the
|
||||||
* pinned "(n/m)" position (hidden when absent); phase 129 (task
|
* pinned count — the phase-193 "N remaining" (hidden when absent);
|
||||||
|
* phase 129 (task
|
||||||
* 04): + .sync-label-chunk, the pinned "· chunk n/m" embed-batch
|
* 04): + .sync-label-chunk, the pinned "· chunk n/m" embed-batch
|
||||||
* position of the in-flight file (hidden in every state but the
|
* position of the in-flight file (hidden in every state but the
|
||||||
* import phase after the file's first batch); phase 152 (task 03):
|
* import phase after the file's first batch); phase 152 (task 03):
|
||||||
@@ -917,7 +921,7 @@ export async function mount(root) {
|
|||||||
* mainText lands in the MAIN part (.sync-label-main — the
|
* mainText lands in the MAIN part (.sync-label-main — the
|
||||||
* shrinks-to-fit prefix + file/folder, the part CSS ellipsizes);
|
* shrinks-to-fit prefix + file/folder, the part CSS ellipsizes);
|
||||||
* countText lands in the COUNT part (.sync-label-count — the pinned
|
* countText lands in the COUNT part (.sync-label-count — the pinned
|
||||||
* "(n/m)" position, NEVER clipped); chunkText (phase 129) lands in
|
* phase-193 "N remaining", NEVER clipped); chunkText (phase 129) lands in
|
||||||
* the CHUNK part (.sync-label-chunk — the pinned "· chunk n/m"
|
* the CHUNK part (.sync-label-chunk — the pinned "· chunk n/m"
|
||||||
* embed-batch position of the in-flight file); imageText (phase
|
* embed-batch position of the in-flight file); imageText (phase
|
||||||
* 152) is the in-flight PDF's "· image n/m" vision-image position —
|
* 152) is the in-flight PDF's "· image n/m" vision-image position —
|
||||||
@@ -1034,9 +1038,20 @@ export async function mount(root) {
|
|||||||
* BARE prefix shows during the clone/pull (sync), before any file
|
* BARE prefix shows during the clone/pull (sync), before any file
|
||||||
* is indexed. Phase 90: the upload run is unpack + register only
|
* is indexed. Phase 90: the upload run is unpack + register only
|
||||||
* (no scan), so its status never carries a file or counts — the
|
* (no scan), so its status never carries a file or counts — the
|
||||||
* "Importing" label is always the bare one. The counts — the PINNED
|
* "Importing" label is always the bare one — and when an upload
|
||||||
* count part, never clipped (Phase 128) — appear only once the
|
* status DOES carry counts the upload job shares this builder and
|
||||||
* import has started (total > 0). The builders return the PARTS
|
* the shape (phase 193, A2: the upload's files_total is the
|
||||||
|
* pre-walk count, the phase-98 contract — the same subtraction
|
||||||
|
* semantics). The count — the PINNED count part, never clipped
|
||||||
|
* (Phase 128) — is the phase-193 COUNT-DOWN part (the owner's
|
||||||
|
* "how many files are left, not how many files out of the
|
||||||
|
* total"): `N remaining` with N = total - done, shown while
|
||||||
|
* N >= 1 and HIDDEN at the final file (N == 0 — the main part
|
||||||
|
* names it; a "0 remaining" next to the last file's name is the
|
||||||
|
* stale-looking edge); plain integer, no thousands separator. The
|
||||||
|
* total > 0 guard stays (the phase-64 pre-walk contract — no count
|
||||||
|
* part before the walk knows the total). The builders return the
|
||||||
|
* PARTS
|
||||||
* ({ main, count }); setSyncLabel writes them (the untruncated full
|
* ({ main, count }); setSyncLabel writes them (the untruncated full
|
||||||
* string — main + " " + count — rides the button title +
|
* string — main + " " + count — rides the button title +
|
||||||
* #sync-progress, A4). Phase 98 (task 02): the SYNC job's
|
* #sync-progress, A4). Phase 98 (task 02): the SYNC job's
|
||||||
@@ -1053,7 +1068,7 @@ export async function mount(root) {
|
|||||||
function fmtSyncLabel(kind, currentFile, done, total) {
|
function fmtSyncLabel(kind, currentFile, done, total) {
|
||||||
const prefix = kind === "upload" ? "Importing" : "Syncing…";
|
const prefix = kind === "upload" ? "Importing" : "Syncing…";
|
||||||
const main = currentFile ? `${prefix} ${currentFile}` : prefix;
|
const main = currentFile ? `${prefix} ${currentFile}` : prefix;
|
||||||
const count = total > 0 ? `(${done}/${total})` : "";
|
const count = total > 0 && total - done > 0 ? `${total - done} remaining` : "";
|
||||||
return { main, count };
|
return { main, count };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1069,8 +1084,11 @@ export async function mount(root) {
|
|||||||
* empty (the summaries phase's own (n/m) is its position — the
|
* empty (the summaries phase's own (n/m) is its position — the
|
||||||
* status's chunk + image fields are cleared at this transition);
|
* status's chunk + image fields are cleared at this transition);
|
||||||
* anything else (the null prelude, "import") → the phase-64
|
* anything else (the null prelude, "import") → the phase-64
|
||||||
* fmtSyncLabel parts, byte-identical (same builder, same fields,
|
* fmtSyncLabel parts (same builder, same fields, same order —
|
||||||
* same order) + the phase-129 CHUNK part: "· chunk n/m" ONLY while
|
* since phase 193 its count part reads `N remaining`, N =
|
||||||
|
* total - done, hidden at the final file; the summaries (n/m)
|
||||||
|
* folder position and the chunk/image parts below stay as they
|
||||||
|
* are) + the phase-129 CHUNK part: "· chunk n/m" ONLY while
|
||||||
* chunks_total > 0 (a file's embed batch is in flight — n the
|
* chunks_total > 0 (a file's embed batch is in flight — n the
|
||||||
* 1-based first chunk of the batch, m the file's total content
|
* 1-based first chunk of the batch, m the file's total content
|
||||||
* chunks, batch granularity), empty before the first batch (0/0) so
|
* chunks, batch granularity), empty before the first batch (0/0) so
|
||||||
@@ -1099,8 +1117,9 @@ export async function mount(root) {
|
|||||||
image: "", // the image position belongs to the import phase only
|
image: "", // the image position belongs to the import phase only
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// null phase (the prelude) or "import" — today's byte-identical
|
// null phase (the prelude) or "import" — the phase-64 label
|
||||||
// sync label (the phase-64 contract, unchanged) + the phase-129
|
// shape (since phase 193 its count part reads `N remaining`) +
|
||||||
|
// the phase-129
|
||||||
// chunk part (hidden before the in-flight file's first batch) +
|
// chunk part (hidden before the in-flight file's first batch) +
|
||||||
// the phase-152 image part (hidden before the in-flight PDF's
|
// the phase-152 image part (hidden before the in-flight PDF's
|
||||||
// first vision describe call).
|
// first vision describe call).
|
||||||
|
|||||||
@@ -2898,14 +2898,16 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
|||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.sync-btn .sync-icon.is-spinning { animation: none; }
|
.sync-btn .sync-icon.is-spinning { animation: none; }
|
||||||
}
|
}
|
||||||
/* Phase 64: the live-file label ("Syncing… <file> (n/m)" / "Importing
|
/* Phase 64: the live-file label ("Syncing… <file> N remaining" /
|
||||||
<file> (n/m)") — a long source/relative/path ellipsizes inside the
|
"Importing <file> N remaining" — phase 193: the file count is the
|
||||||
|
distance to the end, `total - done`, hidden on the final file) — a
|
||||||
|
long source/relative/path ellipsizes inside the
|
||||||
pill; the full path lives in the button title + the #sync-progress
|
pill; the full path lives in the button title + the #sync-progress
|
||||||
box (the aria-live announcer — phase 150: the SOLE sync text
|
box (the aria-live announcer — phase 150: the SOLE sync text
|
||||||
surface). Phase 128 (task 02): the label is a FLEX ROW
|
surface). Phase 128 (task 02): the label is a FLEX ROW
|
||||||
of parts — .sync-label-main (the prefix + file/folder: min-width
|
of parts — .sync-label-main (the prefix + file/folder: min-width
|
||||||
0, the shrinkable item whose ellipsis engages) + .sync-label-count
|
0, the shrinkable item whose ellipsis engages) + .sync-label-count
|
||||||
(the pinned "(n/m)": flex 0 0 auto, NEVER clipped — the
|
(the pinned "N remaining": flex 0 0 auto, NEVER clipped — the
|
||||||
.sync-progress-text mono language, a position reads as data). Phase 129
|
.sync-progress-text mono language, a position reads as data). Phase 129
|
||||||
(task 04): a THIRD pinned part — .sync-label-chunk (the "· chunk
|
(task 04): a THIRD pinned part — .sync-label-chunk (the "· chunk
|
||||||
n/m" embed-batch position of the in-flight file, the same pinned
|
n/m" embed-batch position of the in-flight file, the same pinned
|
||||||
@@ -2925,7 +2927,7 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.sync-label-count {
|
.sync-label-count {
|
||||||
flex: 0 0 auto; /* NEVER clipped — the (n/m) position is the point */
|
flex: 0 0 auto; /* NEVER clipped — the N remaining count is the point */
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-family: var(--mono); /* the .sync-progress-text mono language (position reads as data) */
|
font-family: var(--mono); /* the .sync-progress-text mono language (position reads as data) */
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -490,7 +490,9 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Phase 149 (task 01): the sync detail box — the full UNTRUNCATED live
|
<!-- Phase 149 (task 01): the sync detail box — the full UNTRUNCATED live
|
||||||
label ("Syncing… <source/rel/path> (n/m) · chunk n/m" — phase 152
|
label ("Syncing… <source/rel/path> N remaining · chunk n/m" —
|
||||||
|
phase 193: the file count counts DOWN to the end and hides on
|
||||||
|
the last file; phase 152
|
||||||
appends "· image n/m" while a PDF's vision span is in flight)
|
appends "· image n/m" while a PDF's vision span is in flight)
|
||||||
in a bordered panel under the button row. Phase 150 (task 01) folded the
|
in a bordered panel under the button row. Phase 150 (task 01) folded the
|
||||||
announcer in: the previous live-region span beside it is retired,
|
announcer in: the previous live-region span beside it is retired,
|
||||||
@@ -774,7 +776,7 @@
|
|||||||
inline. Phase 90 (owner-locked A1/A2/A3): the background
|
inline. Phase 90 (owner-locked A1/A2/A3): the background
|
||||||
run is UNPACK + REGISTER ONLY — no model check, no import,
|
run is UNPACK + REGISTER ONLY — no model check, no import,
|
||||||
no overview refresh — so the processing state covers
|
no overview refresh — so the processing state covers
|
||||||
unpack only (no file, no "(n/m)" counts, no title) and
|
unpack only (no file, no "N remaining" count, no title) and
|
||||||
the success line points at the next step: "Uploaded
|
the success line points at the next step: "Uploaded
|
||||||
<name> — press Sync sources to import it." The scan is
|
<name> — press Sync sources to import it." The scan is
|
||||||
the RAG page's Sync button's job (edit the source's
|
the RAG page's Sync button's job (edit the source's
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ poll and the phase-64 slow-LLM proxy is GONE from this suite. The
|
|||||||
"Uploading… → Processing… → restored" lifecycle is made observable the
|
"Uploading… → Processing… → restored" lifecycle is made observable the
|
||||||
old way (the POST is held in the browser via ``page.route``) plus a
|
old way (the POST is held in the browser via ``page.route``) plus a
|
||||||
held FIRST status GET, so the bare "Processing…" in-run label (no
|
held FIRST status GET, so the bare "Processing…" in-run label (no
|
||||||
file, no "(n/m)") is asserted across the whole background run.
|
file, no `N remaining` count) is asserted across the whole background
|
||||||
|
run.
|
||||||
|
|
||||||
The archives are **built in-test** with Python's ``tarfile`` over
|
The archives are **built in-test** with Python's ``tarfile`` over
|
||||||
``tmp_path`` fixture files carrying markdown sentinels (``ALPHA-…`` /
|
``tmp_path`` fixture files carrying markdown sentinels (``ALPHA-…`` /
|
||||||
@@ -65,7 +66,7 @@ Contract under test:
|
|||||||
"Uploading…" while the POST is in flight (held via ``page.route``);
|
"Uploading…" while the POST is in flight (held via ``page.route``);
|
||||||
at the 202 the "Successfully uploaded — <source>" toast fires; the
|
at the 202 the "Successfully uploaded — <source>" toast fires; the
|
||||||
button then carries the BARE "Processing…" label for the whole
|
button then carries the BARE "Processing…" label for the whole
|
||||||
background run (no file, no "(n/m)" — the first status GET is held
|
background run (no file, no `N remaining` count — the first status GET is held
|
||||||
so the in-run window outlives the 2 s poll) until the status
|
so the in-run window outlives the 2 s poll) until the status
|
||||||
``success`` lands: the result line reads "Uploaded e2e-upload
|
``success`` lands: the result line reads "Uploaded e2e-upload
|
||||||
(<stamp>) — press Sync sources to import it." (the phase-154
|
(<stamp>) — press Sync sources to import it." (the phase-154
|
||||||
@@ -373,7 +374,7 @@ def _hold_first_status_fetch(page: Page, hold_s: float) -> None:
|
|||||||
AFTER the page's boot re-attach fetch, before the submit. The
|
AFTER the page's boot re-attach fetch, before the submit. The
|
||||||
poll's first tick fires 2 s after the 202; holding its fetch keeps
|
poll's first tick fires 2 s after the 202; holding its fetch keeps
|
||||||
the button in the in-run state long enough to assert the bare
|
the button in the in-run state long enough to assert the bare
|
||||||
"Processing…" label (no file, no "(n/m)") across the whole
|
"Processing…" label (no file, no `N remaining` count) across the whole
|
||||||
background run — phase 90's run settles in milliseconds, so without
|
background run — phase 90's run settles in milliseconds, so without
|
||||||
the hold the in-run window is only the 2 s pre-tick gap."""
|
the hold the in-run window is only the 2 s pre-tick gap."""
|
||||||
state = {"held": False}
|
state = {"held": False}
|
||||||
@@ -449,7 +450,7 @@ def test_upload_registers_without_indexing(
|
|||||||
button is disabled and reads "Uploading…"; at the 202 the
|
button is disabled and reads "Uploading…"; at the 202 the
|
||||||
"Successfully uploaded — <source>" toast fires (A2); the button
|
"Successfully uploaded — <source>" toast fires (A2); the button
|
||||||
then carries the BARE "Processing…" label for the whole background
|
then carries the BARE "Processing…" label for the whole background
|
||||||
run (no file, no "(n/m)" — phase 90 A2, proven across a held first
|
run (no file, no `N remaining` count — phase 90 A2, proven across a held first
|
||||||
status GET); when the status ``success`` lands the result line
|
status GET); when the status ``success`` lands the result line
|
||||||
reads "Uploaded <source> (<stamp>) — press Sync sources to
|
reads "Uploaded <source> (<stamp>) — press Sync sources to
|
||||||
import it." (A3 — the phase-154 completion stamp), the button
|
import it." (A3 — the phase-154 completion stamp), the button
|
||||||
@@ -499,7 +500,7 @@ def test_upload_registers_without_indexing(
|
|||||||
# The run is in flight (or just settled) server-side — and the
|
# The run is in flight (or just settled) server-side — and the
|
||||||
# label STAYS bare across the whole background run: the first
|
# label STAYS bare across the whole background run: the first
|
||||||
# status GET is held, so the tick that should settle the button is
|
# status GET is held, so the tick that should settle the button is
|
||||||
# in flight — the label carries no file and no "(n/m)".
|
# in flight — the label carries no file and no `N remaining` count.
|
||||||
time.sleep(2.5) # just past the poll's first tick (the fetch is held)
|
time.sleep(2.5) # just past the poll's first tick (the fetch is held)
|
||||||
expect(btn).to_have_text("Processing…")
|
expect(btn).to_have_text("Processing…")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Phase 129 task 05 E2E (Playwright, mock-only): while a file imports,
|
"""Phase 129 task 05 E2E (Playwright, mock-only): while a file imports,
|
||||||
the sync status (endpoint + button label) shows the file's
|
the sync status (endpoint + button label) shows the file's
|
||||||
embedding-chunk position — ``Syncing… chunkprog/big.md (1/2) · chunk
|
embedding-chunk position — ``Syncing… chunkprog/big.md 1 remaining ·
|
||||||
14/38`` — with ``chunks_total`` the file's TRUE content-chunk count and
|
chunk 14/38`` — with ``chunks_total`` the file's TRUE content-chunk count and
|
||||||
``current_chunk`` the 1-based first chunk of the in-flight
|
``current_chunk`` the 1-based first chunk of the in-flight
|
||||||
token-budgeted batch (batch granularity, assumption A6), climbing 1 →
|
token-budgeted batch (batch granularity, assumption A6), climbing 1 →
|
||||||
N through the file's embed batches, restarting per file, and cleared
|
N through the file's embed batches, restarting per file, and cleared
|
||||||
@@ -10,7 +10,10 @@ N through the file's embed batches, restarting per file, and cleared
|
|||||||
The dedicated story suite for ``129_sync_chunk_progress`` (A16 — one
|
The dedicated story suite for ``129_sync_chunk_progress`` (A16 — one
|
||||||
Playwright file per phase, run in isolation): the owner's report was
|
Playwright file per phase, run in isolation): the owner's report was
|
||||||
that ``Syncing… name/of/file/here (2/3)`` "needs to show what chunk
|
that ``Syncing… name/of/file/here (2/3)`` "needs to show what chunk
|
||||||
we're on out of how many".
|
we're on out of how many" (the file count read ``(position/total)``
|
||||||
|
then — phase 193 moved that part to the distance to the end,
|
||||||
|
``N remaining``, on the owner's directive; the ``· chunk`` part this
|
||||||
|
suite is about is untouched).
|
||||||
|
|
||||||
Run in isolation (DB must be up: ``podman compose up -d db``):
|
Run in isolation (DB must be up: ``podman compose up -d db``):
|
||||||
|
|
||||||
@@ -60,9 +63,12 @@ the same host) registered as a ``kind=local`` source (the
|
|||||||
pattern; no git anywhere): ``chunkprog/`` with EXACTLY TWO files, both
|
pattern; no git anywhere): ``chunkprog/`` with EXACTLY TWO files, both
|
||||||
root-level (no subfolders → the summaries candidate set is just the
|
root-level (no subfolders → the summaries candidate set is just the
|
||||||
source root): ``big.md`` (the ~60 KB file above) + ``small.md`` (one
|
source root): ``big.md`` (the ~60 KB file above) + ``small.md`` (one
|
||||||
tiny chunk). The sorted walk processes ``big.md`` FIRST, so the file
|
tiny chunk). The sorted walk processes ``big.md`` FIRST, so the label's
|
||||||
counter reads ``(1/2)`` during the big span and ``(2/2)`` during the
|
phase-193 count part reads ``1 remaining`` during the big span and is
|
||||||
small one — and the small file's single chunk is the proof that the
|
HIDDEN during the small one (the final file: N = total - done = 0 — the
|
||||||
|
endpoint's ``files_done``/``files_total`` stay the 1-based 1/2 then 2/2
|
||||||
|
position; only the LABEL counts down) — and the small file's single
|
||||||
|
chunk is the proof that the
|
||||||
chunk position RESTARTS per file (the big file ends at its last
|
chunk position RESTARTS per file (the big file ends at its last
|
||||||
batch's first index, the small file reports ``(1, 1)``).
|
batch's first index, the small file reports ``(1, 1)``).
|
||||||
|
|
||||||
@@ -98,7 +104,8 @@ Test → observable mapping (Playwright Mapping Rule):
|
|||||||
own 2 s poll): after clicking **Sync sources**, the page's own
|
own 2 s poll): after clicking **Sync sources**, the page's own
|
||||||
poll renders the import span's ``#sync-label`` parts —
|
poll renders the import span's ``#sync-label`` parts —
|
||||||
``.sync-label-main`` == ``Syncing… chunkprog/big.md``,
|
``.sync-label-main`` == ``Syncing… chunkprog/big.md``,
|
||||||
``.sync-label-count`` == ``(1/2)``, and ``.sync-label-chunk``
|
``.sync-label-count`` == ``1 remaining`` (phase 193), and
|
||||||
|
``.sync-label-chunk``
|
||||||
VISIBLE matching ``· chunk \\d+/\\d+`` with the total == the
|
VISIBLE matching ``· chunk \\d+/\\d+`` with the total == the
|
||||||
expected chunk count — and the button ``title`` (and the
|
expected chunk count — and the button ``title`` (and the
|
||||||
``#sync-progress`` box — the aria-live announcer, phase 150: the
|
``#sync-progress`` box — the aria-live announcer, phase 150: the
|
||||||
@@ -177,8 +184,9 @@ SLOW_DELAY_S = "1.5"
|
|||||||
#: the directory's basename is the source name, phase 38).
|
#: the directory's basename is the source name, phase 38).
|
||||||
SOURCE = "chunkprog"
|
SOURCE = "chunkprog"
|
||||||
|
|
||||||
#: The two fixture files (sorted walk: ``big.md`` FIRST → the file
|
#: The two fixture files (sorted walk: ``big.md`` FIRST → the label's
|
||||||
#: counter reads (1/2) on the big span, (2/2) on the small one).
|
#: phase-193 count part reads `1 remaining` on the big span and is
|
||||||
|
#: HIDDEN on the small one — the final file, N = total - done = 0).
|
||||||
BIG_FILE = f"{SOURCE}/big.md"
|
BIG_FILE = f"{SOURCE}/big.md"
|
||||||
SMALL_FILE = f"{SOURCE}/small.md"
|
SMALL_FILE = f"{SOURCE}/small.md"
|
||||||
N_FILES = 2
|
N_FILES = 2
|
||||||
@@ -314,12 +322,12 @@ def _dedup_consecutive(values: list[int]) -> list[int]:
|
|||||||
#: Test 2, step 1 — the in-flight IMPORT label with the VISIBLE chunk
|
#: Test 2, step 1 — the in-flight IMPORT label with the VISIBLE chunk
|
||||||
#: part: the main part carries "Syncing… chunkprog/big.md" (the
|
#: part: the main part carries "Syncing… chunkprog/big.md" (the
|
||||||
#: untruncated A4 text — the big file is the first of the two, so the
|
#: untruncated A4 text — the big file is the first of the two, so the
|
||||||
#: count part is "(1/2)"), the pinned chunk part is visible matching
|
#: phase-193 count part is "1 remaining"), the pinned chunk part is visible matching
|
||||||
#: "· chunk n/m" with m == the expected chunk count (the arg), and the
|
#: "· chunk n/m" with m == the expected chunk count (the arg), and the
|
||||||
#: button title + the #sync-progress box (the aria-live announcer,
|
#: button title + the #sync-progress box (the aria-live announcer,
|
||||||
#: phase 150) carry the UNTRUNCATED concatenation
|
#: phase 150) carry the UNTRUNCATED concatenation
|
||||||
#: (main + " " + count + " " + chunk — A4: the announcer hears the
|
#: (main + " " + count + " " + chunk — A4: the announcer hears the
|
||||||
#: whole "Syncing… <file> (1/2) · chunk n/m").
|
#: whole "Syncing… <file> 1 remaining · chunk n/m").
|
||||||
_JS_IMPORT_CHUNK_SNAPSHOT = """
|
_JS_IMPORT_CHUNK_SNAPSHOT = """
|
||||||
(n) => {
|
(n) => {
|
||||||
const label = document.querySelector('#sync-label');
|
const label = document.querySelector('#sync-label');
|
||||||
@@ -330,7 +338,7 @@ _JS_IMPORT_CHUNK_SNAPSHOT = """
|
|||||||
const chunk = label.querySelector('.sync-label-chunk');
|
const chunk = label.querySelector('.sync-label-chunk');
|
||||||
if (!main || !count || !chunk || count.hidden || chunk.hidden) return null;
|
if (!main || !count || !chunk || count.hidden || chunk.hidden) return null;
|
||||||
if (main.textContent !== 'Syncing… chunkprog/big.md') return null;
|
if (main.textContent !== 'Syncing… chunkprog/big.md') return null;
|
||||||
if (count.textContent !== '(1/2)') return null;
|
if (count.textContent !== '1 remaining') return null;
|
||||||
const m = chunk.textContent.match(/^· chunk (\\d+)\\/(\\d+)$/);
|
const m = chunk.textContent.match(/^· chunk (\\d+)\\/(\\d+)$/);
|
||||||
if (!m || Number(m[2]) !== n) return null;
|
if (!m || Number(m[2]) !== n) return null;
|
||||||
const full = main.textContent + ' ' + count.textContent + ' ' + chunk.textContent;
|
const full = main.textContent + ' ' + count.textContent + ' ' + chunk.textContent;
|
||||||
@@ -861,8 +869,8 @@ def test_endpoint_reports_the_chunk_position(
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 2. UI — the label's chunk part: "Syncing… chunkprog/big.md (1/2) ·
|
# 2. UI — the label's chunk part: "Syncing… chunkprog/big.md
|
||||||
# chunk n/m" while a batch is in flight, the untruncated label on
|
# 1 remaining · chunk n/m" while a batch is in flight, the untruncated label on
|
||||||
# the title + announcer, the part cleared after the file's span
|
# the title + announcer, the part cleared after the file's span
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -873,7 +881,8 @@ def test_label_shows_the_chunk_part(
|
|||||||
"""A fresh admin page on ``/sources.html`` clicks **Sync sources**;
|
"""A fresh admin page on ``/sources.html`` clicks **Sync sources**;
|
||||||
the page's OWN 2 s poll renders the import span's ``#sync-label``
|
the page's OWN 2 s poll renders the import span's ``#sync-label``
|
||||||
as the phase-128/129 PARTS — the main part
|
as the phase-128/129 PARTS — the main part
|
||||||
``Syncing… chunkprog/big.md``, the pinned count part ``(1/2)``,
|
``Syncing… chunkprog/big.md``, the pinned phase-193 count part
|
||||||
|
``1 remaining`` (the big file is the first of two — N = total − done),
|
||||||
and the VISIBLE chunk part matching ``· chunk n/m`` with the total
|
and the VISIBLE chunk part matching ``· chunk n/m`` with the total
|
||||||
== the expected chunk count (the big file's span is ~45 s under
|
== the expected chunk count (the big file's span is ~45 s under
|
||||||
the slow proxy — the poll cannot miss it) — with the button
|
the slow proxy — the poll cannot miss it) — with the button
|
||||||
@@ -903,14 +912,14 @@ def test_label_shows_the_chunk_part(
|
|||||||
_JS_IMPORT_CHUNK_SNAPSHOT, arg=n_chunks, timeout=SETTLE_TIMEOUT_MS
|
_JS_IMPORT_CHUNK_SNAPSHOT, arg=n_chunks, timeout=SETTLE_TIMEOUT_MS
|
||||||
).json_value()
|
).json_value()
|
||||||
assert snap["mainText"] == f"Syncing… {SOURCE}/big.md", snap
|
assert snap["mainText"] == f"Syncing… {SOURCE}/big.md", snap
|
||||||
assert snap["countText"] == "(1/2)", snap
|
assert snap["countText"] == "1 remaining", snap
|
||||||
m = CHUNK_PART_RE.match(snap["chunkText"])
|
m = CHUNK_PART_RE.match(snap["chunkText"])
|
||||||
assert m, snap
|
assert m, snap
|
||||||
assert int(m.group(2)) == n_chunks, snap # the TRUE chunk count, not a batch count
|
assert int(m.group(2)) == n_chunks, snap # the TRUE chunk count, not a batch count
|
||||||
assert int(m.group(1)) >= 1, snap
|
assert int(m.group(1)) >= 1, snap
|
||||||
# A4: the untruncated concatenation rides the title AND the
|
# A4: the untruncated concatenation rides the title AND the
|
||||||
# aria-live announcer (the "· chunk" part included).
|
# aria-live announcer (the "· chunk" part included).
|
||||||
full = f"Syncing… {SOURCE}/big.md (1/2) {snap['chunkText']}"
|
full = f"Syncing… {SOURCE}/big.md 1 remaining {snap['chunkText']}"
|
||||||
assert snap["title"] == full and "· chunk" in snap["title"], snap
|
assert snap["title"] == full and "· chunk" in snap["title"], snap
|
||||||
# The box IS the announcer (phase 150) — it carries the same full
|
# The box IS the announcer (phase 150) — it carries the same full
|
||||||
# string the title does.
|
# string the title does.
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ Test → observable mapping (Playwright Mapping Rule):
|
|||||||
first ``· image `` label — the two positions are distinct) and
|
first ``· image `` label — the two positions are distinct) and
|
||||||
every image-span label carries the chunk part WITH IT (the last
|
every image-span label carries the chunk part WITH IT (the last
|
||||||
chunk position persists through the file's image span —
|
chunk position persists through the file's image span —
|
||||||
"… (1/1) · chunk 19/20 · image 3/4"), its total == the file's
|
"Syncing… imgpass/imgpass.pdf · chunk 19/20 · image 3/4" — no
|
||||||
|
``N remaining`` part, phase 193 hides it on this final file), its
|
||||||
|
total == the file's
|
||||||
TRUE chunk count; (c) the button ``title`` + ``aria-label`` carry
|
TRUE chunk count; (c) the button ``title`` + ``aria-label`` carry
|
||||||
the SAME full string as the box on every running sample (A4 —
|
the SAME full string as the box on every running sample (A4 —
|
||||||
asserted per sample, atomically); (d) at settle the box shows the
|
asserted per sample, atomically); (d) at settle the box shows the
|
||||||
@@ -109,6 +111,13 @@ Test → observable mapping (Playwright Mapping Rule):
|
|||||||
running sync re-enters with the WHOLE status, phase 98) renders
|
running sync re-enters with the WHOLE status, phase 98) renders
|
||||||
the CURRENT image position with the A4 title/aria — then the run
|
the CURRENT image position with the A4 title/aria — then the run
|
||||||
settles to the counts line.
|
settles to the counts line.
|
||||||
|
|
||||||
|
Phase 193 (task 02) moves the running file-count part to the distance
|
||||||
|
to the end — ``N remaining``, ``N = files_total - files_done`` — and
|
||||||
|
this fixture makes that edge VISIBLE rather than pinned away: its ONE
|
||||||
|
file is also its FINAL file (``N == 0``), so the import span renders NO
|
||||||
|
count part at all (the main part names the last file). The ``· chunk`` /
|
||||||
|
``· image`` positions and the terminal counts line are byte-identical.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -169,8 +178,9 @@ SLOW_DELAY_S = "1.5"
|
|||||||
#: The local source — the temp directory's basename (``kind=local`` →
|
#: The local source — the temp directory's basename (``kind=local`` →
|
||||||
#: the directory's basename is the source name, phase 38).
|
#: the directory's basename is the source name, phase 38).
|
||||||
SOURCE = "imgpass"
|
SOURCE = "imgpass"
|
||||||
#: The single fixture file (the source is one file — the count part
|
#: The single fixture file (the source is one file, so phase 193 hides
|
||||||
#: reads "(1/1)" throughout the import phase).
|
#: the count part for the whole import span — N = total - done = 0 on
|
||||||
|
#: the only file there is).
|
||||||
PDF_NAME = "imgpass.pdf"
|
PDF_NAME = "imgpass.pdf"
|
||||||
N_FILES = 1
|
N_FILES = 1
|
||||||
|
|
||||||
@@ -739,8 +749,14 @@ def test_label_advances_through_the_image_span(
|
|||||||
# sampled) ---
|
# sampled) ---
|
||||||
assert any(t == "Syncing…" for t in seen), f"no bare prelude label: {seen[:6]}"
|
assert any(t == "Syncing…" for t in seen), f"no bare prelude label: {seen[:6]}"
|
||||||
import_labels = [t for t in seen if t.startswith("Syncing… ")]
|
import_labels = [t for t in seen if t.startswith("Syncing… ")]
|
||||||
assert any(f"({N_FILES}/{N_FILES})" in t for t in import_labels), (
|
assert any(f"Syncing… {SOURCE}/{PDF_NAME}" in t for t in import_labels), (
|
||||||
f"no file count part: {import_labels[:6]}"
|
f"no live-file label: {import_labels[:6]}"
|
||||||
|
)
|
||||||
|
# Phase 193 (A1): the count part counts DOWN and hides at the FINAL
|
||||||
|
# file — this fixture's one file IS its final file (N = 0), so no
|
||||||
|
# running label carries it (the pre-phase "(1/1)" is gone).
|
||||||
|
assert all(" remaining" not in t for t in import_labels), (
|
||||||
|
f"the final file must render no count part: {import_labels[:6]}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- the chunk part appeared EARLIER in the run (the two
|
# --- the chunk part appeared EARLIER in the run (the two
|
||||||
@@ -783,8 +799,9 @@ def test_label_advances_through_the_image_span(
|
|||||||
ns = [n for n, _ in positions]
|
ns = [n for n, _ in positions]
|
||||||
assert ns == sorted(ns), f"the climb is backwards somewhere: {positions}"
|
assert ns == sorted(ns), f"the climb is backwards somewhere: {positions}"
|
||||||
assert N_IMAGES in ns, f"the climb never reached the total: {positions}"
|
assert N_IMAGES in ns, f"the climb never reached the total: {positions}"
|
||||||
# The two positions COEXIST (distinct and both may show — "… (1/1)
|
# The two positions COEXIST (distinct and both may show —
|
||||||
# · chunk 19/20 · image 3/4"): every image-span label carries the
|
# "Syncing… imgpass/imgpass.pdf · chunk 19/20 · image 3/4", with no
|
||||||
|
# `N remaining` part on this single-file run — phase 193): every image-span label carries the
|
||||||
# chunk part with it (the last chunk position persists through the
|
# chunk part with it (the last chunk position persists through the
|
||||||
# file's image span — the per-file reset + the phase transitions
|
# file's image span — the per-file reset + the phase transitions
|
||||||
# clear it, not the end of the chunk span).
|
# clear it, not the end of the chunk span).
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ Phase 150 folded the announcer into the box: it is now the SOLE
|
|||||||
sync text surface AND the sole aria-live region (the retired
|
sync text surface AND the sole aria-live region (the retired
|
||||||
announcer span is gone). Tests 1–3
|
announcer span is gone). Tests 1–3
|
||||||
therefore pin the BOX: visible while the run is in flight, its text
|
therefore pin the BOX: visible while the run is in flight, its text
|
||||||
the pre-phase byte-identical full string ("Syncing… <full
|
the full label ("Syncing… <full
|
||||||
source/relative/path> (n/4)" / "Summarizing folders… <folder>
|
source/relative/path> N remaining" / "Summarizing folders… <folder>
|
||||||
(n/3)" + the phase-129 "· chunk n/m" while a batch is in flight),
|
(n/3)" + the phase-129 "· chunk n/m" while a batch is in flight),
|
||||||
inside the viewport at 390 px and 1280 px, the button in its
|
inside the viewport at 390 px and 1280 px, the button in its
|
||||||
running state, and the ``title`` carrying the same full string
|
running state, and the ``title`` carrying the same full string
|
||||||
@@ -47,6 +47,18 @@ pre-phase, that rule simply did not exist, so the faithful
|
|||||||
pre-phase counterfactual restores the parts' inline-block display
|
pre-phase counterfactual restores the parts' inline-block display
|
||||||
at matching specificity, last in the cascade.
|
at matching specificity, last in the cascade.
|
||||||
|
|
||||||
|
Phase 193 (task 02 — the owner's "how many files are LEFT, not how
|
||||||
|
many files out of the total") re-points the import label's COUNT part:
|
||||||
|
the distance to the end — ``N remaining`` with ``N = total - done`` —
|
||||||
|
shown while ``N >= 1`` and HIDDEN at the final file (``N == 0``; the
|
||||||
|
main part names the file in flight). ``files_done`` is the importer's
|
||||||
|
1-based CURRENT file, so on this 4-file fixture the count-bearing
|
||||||
|
frames are ``3 remaining`` / ``2 remaining`` / ``1 remaining`` and the
|
||||||
|
LAST file renders no count part — the count pins below therefore name
|
||||||
|
``[1-3] remaining``, and every wait resolves on a count-bearing frame.
|
||||||
|
The summaries ``(n/3)`` folder position, the phase-129 chunk part and
|
||||||
|
the terminal counts line are byte-identical.
|
||||||
|
|
||||||
Run in isolation (DB must be up: ``podman compose up -d db``):
|
Run in isolation (DB must be up: ``podman compose up -d db``):
|
||||||
|
|
||||||
uv run pytest tests/e2e/test_sync_label_mobile.py -v --no-cov
|
uv run pytest tests/e2e/test_sync_label_mobile.py -v --no-cov
|
||||||
@@ -90,9 +102,9 @@ Test → observable mapping (Playwright Mapping Rule):
|
|||||||
**Sync sources**; while the import phase is running (the page's
|
**Sync sources**; while the import phase is running (the page's
|
||||||
own 2 s poll): the detail box (``#sync-progress``) is VISIBLE and
|
own 2 s poll): the detail box (``#sync-progress``) is VISIBLE and
|
||||||
an atomic snapshot of: its text the FULL untruncated label
|
an atomic snapshot of: its text the FULL untruncated label
|
||||||
``Syncing… <full source/relative/path> (n/4)`` (+ the phase-129
|
``Syncing… <full source/relative/path> N remaining`` (+ the phase-129
|
||||||
"· chunk n/m" part while a batch is in flight — optional, the
|
"· chunk n/m" part while a batch is in flight — optional, the
|
||||||
pre-first-batch frame has none) with the ``(n/4)`` count IN the
|
pre-first-batch frame has none) with the ``N remaining`` count IN the
|
||||||
text (never clipped — nothing is ellipsized away), the box's
|
text (never clipped — nothing is ellipsized away), the box's
|
||||||
bounding box INSIDE the viewport (left ≥ 0, right ≤ 390 — the
|
bounding box INSIDE the viewport (left ≥ 0, right ≤ 390 — the
|
||||||
bug's direct negation), the button in its running state
|
bug's direct negation), the button in its running state
|
||||||
@@ -110,10 +122,10 @@ Test → observable mapping (Playwright Mapping Rule):
|
|||||||
count span is hidden + empty, and the box hides.
|
count span is hidden + empty, and the box hides.
|
||||||
3. ``test_desktop_label_unchanged`` — viewport 1280×800; the same
|
3. ``test_desktop_label_unchanged`` — viewport 1280×800; the same
|
||||||
sync: during import the box text matches ``Syncing… <full
|
sync: during import the box text matches ``Syncing… <full
|
||||||
source/relative/path> (n/m)`` (+ the phase-129 chunk part while a
|
source/relative/path> N remaining`` (+ the phase-129 chunk part
|
||||||
batch is in flight) — the pre-phase byte-identical FULL string
|
while a batch is in flight) — the FULL string (phase 149 moved it
|
||||||
(phase 149 moved it from the pill's parts to the box — the text
|
from the pill's parts to the box, phase 193 re-shaped the count
|
||||||
is unchanged), the box inside the viewport, the button in its
|
part), the box inside the viewport, the button in its
|
||||||
running state (``is-running``), and the ``title`` carrying the
|
running state (``is-running``), and the ``title`` carrying the
|
||||||
same untruncated string (A4).
|
same untruncated string (A4).
|
||||||
4. ``test_pre_phase_css_reproduces_the_clip`` — the pin check: the
|
4. ``test_pre_phase_css_reproduces_the_clip`` — the pin check: the
|
||||||
@@ -210,12 +222,14 @@ FILE_RE = r"synclabel/(?:alpha|bravo)/(?:alpha|bravo)-[ab]\.md"
|
|||||||
|
|
||||||
#: The in-flight import label's PARTS (the phase-64 byte-identical
|
#: The in-flight import label's PARTS (the phase-64 byte-identical
|
||||||
#: strings, unit-pinned in tests/unit/test_frontend_sync_upload.py):
|
#: strings, unit-pinned in tests/unit/test_frontend_sync_upload.py):
|
||||||
#: main "Syncing… <full source/relative/path>" + count "(n/4)" —
|
#: main "Syncing… <full source/relative/path>" + count "N remaining"
|
||||||
#: ``done`` starts at 1 (the importer counts the file as current
|
#: (phase 193) — ``files_done`` starts at 1 (the importer counts the
|
||||||
#: BEFORE indexing it), so ``n`` ∈ 1..4. (Test 4 — the pre-phase
|
#: file as current BEFORE indexing it) and the part hides at
|
||||||
|
#: ``total - done == 0``, so ``N`` ∈ 1..3 on this 4-file fixture and
|
||||||
|
#: the LAST file renders no count part at all. (Test 4 — the pre-phase
|
||||||
#: rules' clip reproduction — pins the PARTS' DOM text directly.)
|
#: rules' clip reproduction — pins the PARTS' DOM text directly.)
|
||||||
IMPORT_MAIN_RE = re.compile(rf"^Syncing… {FILE_RE}$")
|
IMPORT_MAIN_RE = re.compile(rf"^Syncing… {FILE_RE}$")
|
||||||
IMPORT_COUNT_RE = re.compile(r"^\([1-4]/4\)$")
|
IMPORT_COUNT_RE = re.compile(r"^[1-3] remaining$")
|
||||||
#: The pre-phase byte-identical FULL string (main + " " + count — the
|
#: The pre-phase byte-identical FULL string (main + " " + count — the
|
||||||
#: single span's text before phase 128; the A4 title keeps it), plus
|
#: single span's text before phase 128; the A4 title keeps it), plus
|
||||||
#: the OPTIONAL phase-129 chunk part (" · chunk n/m" — the in-flight
|
#: the OPTIONAL phase-129 chunk part (" · chunk n/m" — the in-flight
|
||||||
@@ -223,7 +237,9 @@ IMPORT_COUNT_RE = re.compile(r"^\([1-4]/4\)$")
|
|||||||
#: 01): the box (#sync-progress) carries this EXACT string while the
|
#: 01): the box (#sync-progress) carries this EXACT string while the
|
||||||
#: import is in flight (tests 1 + 3) — the title carries it too
|
#: import is in flight (tests 1 + 3) — the title carries it too
|
||||||
#: (A4 — one source, two surfaces).
|
#: (A4 — one source, two surfaces).
|
||||||
IMPORT_FULL_RE = re.compile(rf"^Syncing… {FILE_RE} \([1-4]/4\)(?: · chunk \d+/\d+)?$")
|
IMPORT_FULL_RE = re.compile(
|
||||||
|
rf"^Syncing… {FILE_RE} [1-3] remaining(?: · chunk \d+/\d+)?$"
|
||||||
|
)
|
||||||
|
|
||||||
#: The summaries label's FULL string (phase 98 task 02, unit-pinned):
|
#: The summaries label's FULL string (phase 98 task 02, unit-pinned):
|
||||||
#: "Summarizing folders… [source/folder] (n/3)" (the folder part
|
#: "Summarizing folders… [source/folder] (n/3)" (the folder part
|
||||||
@@ -280,7 +296,9 @@ def _md(title: str, body: str) -> str:
|
|||||||
|
|
||||||
#: Test 1/3 — the in-flight IMPORT detail: the box is VISIBLE and its
|
#: Test 1/3 — the in-flight IMPORT detail: the box is VISIBLE and its
|
||||||
#: text is the FULL untruncated label — "Syncing… <full
|
#: text is the FULL untruncated label — "Syncing… <full
|
||||||
#: source/relative/path> (n/4)" (+ the optional phase-129 " · chunk
|
#: source/relative/path> N remaining" (phase 193 — ``N`` ∈ 1..3 here;
|
||||||
|
#: the final file carries no count part, so the wait resolves on a
|
||||||
|
#: count-bearing frame) (+ the optional phase-129 " · chunk
|
||||||
#: n/m" part while a file's embed batch is in flight — the
|
#: n/m" part while a file's embed batch is in flight — the
|
||||||
#: pre-first-batch frame has none; the phase-129 dedicated suite pins
|
#: pre-first-batch frame has none; the phase-129 dedicated suite pins
|
||||||
#: it strictly) — the count is IN the text (never clipped — nothing
|
#: it strictly) — the count is IN the text (never clipped — nothing
|
||||||
@@ -296,7 +314,7 @@ _JS_IMPORT_BOX_SNAPSHOT = """
|
|||||||
const t = textEl.textContent;
|
const t = textEl.textContent;
|
||||||
const reFull = new RegExp(
|
const reFull = new RegExp(
|
||||||
"^Syncing… synclabel/(?:alpha|bravo)/(?:alpha|bravo)-[ab]\\\\.md " +
|
"^Syncing… synclabel/(?:alpha|bravo)/(?:alpha|bravo)-[ab]\\\\.md " +
|
||||||
"\\\\([1-4]/4\\\\)(?: · chunk \\\\d+/\\\\d+)?$"
|
"[1-3] remaining(?: · chunk \\\\d+/\\\\d+)?$"
|
||||||
);
|
);
|
||||||
if (!reFull.test(t)) return null;
|
if (!reFull.test(t)) return null;
|
||||||
const title = btn.getAttribute('title');
|
const title = btn.getAttribute('title');
|
||||||
@@ -395,7 +413,7 @@ _JS_PARTS_IMPORT_SNAPSHOT = """
|
|||||||
const count = label.querySelector('.sync-label-count');
|
const count = label.querySelector('.sync-label-count');
|
||||||
if (!main || !count || count.hidden) return null;
|
if (!main || !count || count.hidden) return null;
|
||||||
const reMain = /^Syncing… synclabel\\/(?:alpha|bravo)\\/(?:alpha|bravo)-[ab]\\.md$/;
|
const reMain = /^Syncing… synclabel\\/(?:alpha|bravo)\\/(?:alpha|bravo)-[ab]\\.md$/;
|
||||||
const reCount = /^\\([1-4]\\/4\\)$/;
|
const reCount = /^[1-3] remaining$/;
|
||||||
if (!reMain.test(main.textContent) || !reCount.test(count.textContent)) return null;
|
if (!reMain.test(main.textContent) || !reCount.test(count.textContent)) return null;
|
||||||
const chunk = label.querySelector('.sync-label-chunk');
|
const chunk = label.querySelector('.sync-label-chunk');
|
||||||
const chunkText = (chunk && !chunk.hidden) ? chunk.textContent : '';
|
const chunkText = (chunk && !chunk.hidden) ? chunk.textContent : '';
|
||||||
@@ -701,7 +719,7 @@ def _assert_box_fits_viewport(snap: dict[str, Any], viewport_w: int) -> None:
|
|||||||
box is a block in the 72rem centered container, so it can only
|
box is a block in the 72rem centered container, so it can only
|
||||||
shrink toward the container's content width — it never overflows
|
shrink toward the container's content width — it never overflows
|
||||||
the viewport — and its text is the FULL untruncated label (the
|
the viewport — and its text is the FULL untruncated label (the
|
||||||
"(n/m)" count is IN the text; the wrap that keeps long paths
|
"N remaining" count is IN the text; the wrap that keeps long paths
|
||||||
on-screen is task 02's overflow-wrap: anywhere, pinned by the
|
on-screen is task 02's overflow-wrap: anywhere, pinned by the
|
||||||
phase-149 dedicated suite). The 1 px slack covers float rounding
|
phase-149 dedicated suite). The 1 px slack covers float rounding
|
||||||
only."""
|
only."""
|
||||||
@@ -718,7 +736,7 @@ def _assert_box_fits_viewport(snap: dict[str, Any], viewport_w: int) -> None:
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 1. Mobile (390×844) — import: the label fits the viewport and the
|
# 1. Mobile (390×844) — import: the label fits the viewport and the
|
||||||
# (n/m) count is visible, never clipped
|
# N remaining count is visible, never clipped
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -732,9 +750,10 @@ def test_mobile_label_fits_viewport_with_count_in_import(
|
|||||||
(``#sync-progress`` — phase 149: the running detail left the
|
(``#sync-progress`` — phase 149: the running detail left the
|
||||||
pill; the button is the spinner) is an atomic snapshot of:
|
pill; the button is the spinner) is an atomic snapshot of:
|
||||||
VISIBLE with the FULL untruncated label ``Syncing… <full
|
VISIBLE with the FULL untruncated label ``Syncing… <full
|
||||||
source/relative/path> (n/4)`` (+ the phase-129 "· chunk n/m" part
|
source/relative/path> N remaining`` (+ the phase-129 "· chunk n/m"
|
||||||
while a batch is in flight — optional) with the ``(n/4)`` count IN
|
part while a batch is in flight — optional) with the
|
||||||
the text (NEVER clipped — nothing is ellipsized away), the box's
|
``N remaining`` count IN the text (NEVER clipped — nothing is
|
||||||
|
ellipsized away), the box's
|
||||||
bounding box INSIDE the viewport (left ≥ 0, right ≤ 390 — the
|
bounding box INSIDE the viewport (left ≥ 0, right ≤ 390 — the
|
||||||
bug's direct negation), the button in its running state
|
bug's direct negation), the button in its running state
|
||||||
(``is-running``), and the button ``title`` carrying the SAME full
|
(``is-running``), and the button ``title`` carrying the SAME full
|
||||||
@@ -752,7 +771,7 @@ def test_mobile_label_fits_viewport_with_count_in_import(
|
|||||||
).json_value()
|
).json_value()
|
||||||
|
|
||||||
# The FULL untruncated label — the complete
|
# The FULL untruncated label — the complete
|
||||||
# source/relative/path, the (n/4) count IN the text (never
|
# source/relative/path, the N remaining count IN the text (never
|
||||||
# clipped), the optional phase-129 chunk part.
|
# clipped), the optional phase-129 chunk part.
|
||||||
assert IMPORT_FULL_RE.match(snap["boxText"]), snap["boxText"]
|
assert IMPORT_FULL_RE.match(snap["boxText"]), snap["boxText"]
|
||||||
# A4: the button title carries the SAME untruncated label (the
|
# A4: the button title carries the SAME untruncated label (the
|
||||||
@@ -819,9 +838,10 @@ def test_desktop_label_unchanged(
|
|||||||
phase 149 moved the running detail out of the pill to the box;
|
phase 149 moved the running detail out of the pill to the box;
|
||||||
the owner's report was that the full label was clipped in the
|
the owner's report was that the full label was clipped in the
|
||||||
pill even on desktop) carries ``Syncing… <full
|
pill even on desktop) carries ``Syncing… <full
|
||||||
source/relative/path> (n/m)`` (+ the phase-129 chunk part while a
|
source/relative/path> N remaining`` (+ the phase-129 chunk part
|
||||||
batch is in flight): the pre-phase byte-identical FULL string
|
while a batch is in flight): the FULL string (phase 149 changed
|
||||||
(phase 149 changed the SURFACE, not the text). The box is inside
|
the SURFACE, phase 193 the count part — the desktop keeps the
|
||||||
|
same text as the phone). The box is inside
|
||||||
the viewport, the button is in its running state (``is-running``
|
the viewport, the button is in its running state (``is-running``
|
||||||
— the spinner), and the button ``title`` carries the same
|
— the spinner), and the button ``title`` carries the same
|
||||||
untruncated string (A4)."""
|
untruncated string (A4)."""
|
||||||
@@ -832,9 +852,9 @@ def test_desktop_label_unchanged(
|
|||||||
_JS_IMPORT_BOX_SNAPSHOT, timeout=SETTLE_TIMEOUT_MS
|
_JS_IMPORT_BOX_SNAPSHOT, timeout=SETTLE_TIMEOUT_MS
|
||||||
).json_value()
|
).json_value()
|
||||||
|
|
||||||
# The pre-phase byte-identical FULL string ("Syncing… <full
|
# The FULL string ("Syncing… <full source/relative/path>
|
||||||
# source/relative/path> (n/m)" + " · chunk n/m" while a batch is
|
# N remaining" + " · chunk n/m" while a batch is in flight) — in
|
||||||
# in flight) — now in the box, untruncated and wrapping.
|
# the box, untruncated and wrapping.
|
||||||
assert IMPORT_FULL_RE.match(snap["boxText"]), snap["boxText"]
|
assert IMPORT_FULL_RE.match(snap["boxText"]), snap["boxText"]
|
||||||
assert snap["title"] == snap["boxText"], snap
|
assert snap["title"] == snap["boxText"], snap
|
||||||
# Phase 149: the running button is the spinner (is-running);
|
# Phase 149: the running button is the spinner (is-running);
|
||||||
@@ -900,7 +920,8 @@ def test_pre_phase_css_reproduces_the_clip(
|
|||||||
# The clip: the count's box runs past the label's clip edge — the
|
# The clip: the count's box runs past the label's clip edge — the
|
||||||
# label's overflow: hidden paints it away (the probe measured the
|
# label's overflow: hidden paints it away (the probe measured the
|
||||||
# count FULLY past the edge at 390 px with the repo's fonts — the
|
# count FULLY past the edge at 390 px with the repo's fonts — the
|
||||||
# "(n/m) doesn't show up on mobile" report).
|
# "(n/m) doesn't show up on mobile" report — the owner's words
|
||||||
|
# before phase 193 turned the part into `N remaining`).
|
||||||
assert count["right"] > label["right"] + 1, (
|
assert count["right"] > label["right"] + 1, (
|
||||||
f"pre-phase CSS did not clip the count — the pin would be vacuous: "
|
f"pre-phase CSS did not clip the count — the pin would be vacuous: "
|
||||||
f"label={label} count={count}"
|
f"label={label} count={count}"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ the **button is the loading spinner** (``is-running`` hides the label
|
|||||||
parts; the spinning icon is the whole control) and the bordered text
|
parts; the spinning icon is the whole control) and the bordered text
|
||||||
box directly under the button row (``#sync-progress``) carries the
|
box directly under the button row (``#sync-progress``) carries the
|
||||||
**EXACT full untruncated live label** — ``Syncing…
|
**EXACT full untruncated live label** — ``Syncing…
|
||||||
<source/relative/path> (n/m) · chunk n/m`` — wrapping, never clipping
|
<source/relative/path> N remaining · chunk n/m`` — wrapping, never clipping
|
||||||
(the long ``source/relative/path`` that overflows the pill is the
|
(the long ``source/relative/path`` that overflows the pill is the
|
||||||
fixture's whole reason to exist). Phase 150 folded the announcer into
|
fixture's whole reason to exist). Phase 150 folded the announcer into
|
||||||
the box: it is the **single sync text surface AND the sole aria-live
|
the box: it is the **single sync text surface AND the sole aria-live
|
||||||
@@ -22,6 +22,12 @@ behavioral proof through the REAL sync path: a real ``git clone`` of a
|
|||||||
local ``file://`` fixture repo, a real import, the slowed mock LLM so
|
local ``file://`` fixture repo, a real import, the slowed mock LLM so
|
||||||
the in-flight state is observable.
|
the in-flight state is observable.
|
||||||
|
|
||||||
|
Phase 193 (task 02) re-points the file-count part of that label to the
|
||||||
|
distance to the end — ``N remaining`` with ``N = total - done`` — so the
|
||||||
|
long file (the FIRST of the three) renders ``2 remaining`` while the
|
||||||
|
endpoint's ``files_done``/``files_total`` stay the 1-based 1/3 position.
|
||||||
|
The ``· chunk`` part and the terminal counts line are byte-identical.
|
||||||
|
|
||||||
Run in isolation (DB must be up: ``podman compose up -d db``; git on PATH —
|
Run in isolation (DB must be up: ``podman compose up -d db``; git on PATH —
|
||||||
a documented environment prerequisite, phase 28):
|
a documented environment prerequisite, phase 28):
|
||||||
|
|
||||||
@@ -105,7 +111,7 @@ in one JS task):
|
|||||||
phase-134 contract unchanged).
|
phase-134 contract unchanged).
|
||||||
3. ``test_box_carries_the_full_untruncated_label`` — during the
|
3. ``test_box_carries_the_full_untruncated_label`` — during the
|
||||||
in-flight window of the LONG-path file (poll until the box text is
|
in-flight window of the LONG-path file (poll until the box text is
|
||||||
``Syncing… <full long path> (1/3)`` — the path string IN FULL, the
|
``Syncing… <full long path> 2 remaining`` — the path string IN FULL, the
|
||||||
pre-phase ellipsis would have truncated it): the box's
|
pre-phase ellipsis would have truncated it): the box's
|
||||||
``scrollWidth <= clientWidth`` AND the text's ``scrollWidth <=
|
``scrollWidth <= clientWidth`` AND the text's ``scrollWidth <=
|
||||||
clientWidth`` (the wrap engaged — nothing is clipped), the box IS the
|
clientWidth`` (the wrap engaged — nothing is clipped), the box IS the
|
||||||
@@ -114,7 +120,7 @@ in one JS task):
|
|||||||
box's text too, and the box ink on the panel background clears WCAG
|
box's text too, and the box ink on the panel background clears WCAG
|
||||||
2.1 AA (≥ 4.5:1). Then a retry loop over the slow embed window pins
|
2.1 AA (≥ 4.5:1). Then a retry loop over the slow embed window pins
|
||||||
the phase-129 chunk part IN THE BOX (``Syncing… <full long path>
|
the phase-129 chunk part IN THE BOX (``Syncing… <full long path>
|
||||||
(1/3) · chunk n/m`` — the part is only present while an embed batch
|
2 remaining · chunk n/m`` — the part is only present while an embed batch
|
||||||
is in flight; the proxy's 1.5 s delay makes the ~15 s window ≫ the
|
is in flight; the proxy's 1.5 s delay makes the ~15 s window ≫ the
|
||||||
2 s poll).
|
2 s poll).
|
||||||
4. ``test_mobile_box_full_width`` — a fresh page at the house mobile
|
4. ``test_mobile_box_full_width`` — a fresh page at the house mobile
|
||||||
@@ -226,7 +232,8 @@ N_FILES = 3
|
|||||||
#: The in-flight label's file part — the status endpoint's
|
#: The in-flight label's file part — the status endpoint's
|
||||||
#: ``current_file`` = ``<source>/<rel>`` (the sync's progress hook),
|
#: ``current_file`` = ``<source>/<rel>`` (the sync's progress hook),
|
||||||
#: verbatim. The long file is the FIRST of the three (the sorted walk —
|
#: verbatim. The long file is the FIRST of the three (the sorted walk —
|
||||||
#: ``notes/a-…`` < ``notes/short-…``), so its count part is ``(1/3)``.
|
#: ``notes/a-…`` < ``notes/short-…``), so its phase-193 count part is
|
||||||
|
#: ``2 remaining`` (N = total - done = 3 - 1).
|
||||||
LONG_PATH = f"{SOURCE}/{LONG_REL}"
|
LONG_PATH = f"{SOURCE}/{LONG_REL}"
|
||||||
|
|
||||||
#: The long file's shape (module docstring): an H1 preamble (one chunk)
|
#: The long file's shape (module docstring): an H1 preamble (one chunk)
|
||||||
@@ -370,7 +377,7 @@ _JS_RUNNING_SNAPSHOT = """
|
|||||||
|
|
||||||
#: Test 3, wait A — the in-flight window of the LONG-path file: the box
|
#: Test 3, wait A — the in-flight window of the LONG-path file: the box
|
||||||
#: is VISIBLE and its text is the FULL label for the long file —
|
#: is VISIBLE and its text is the FULL label for the long file —
|
||||||
#: "Syncing… <full long path> (1/3)" (+ the optional phase-129
|
#: "Syncing… <full long path> 2 remaining" (+ the optional phase-129
|
||||||
#: " · chunk n/m" part while an embed batch is in flight — the
|
#: " · chunk n/m" part while an embed batch is in flight — the
|
||||||
#: pre-first-batch frame has none) — the complete
|
#: pre-first-batch frame has none) — the complete
|
||||||
# source/relative/path IN the text (the pre-phase ellipsis would have
|
# source/relative/path IN the text (the pre-phase ellipsis would have
|
||||||
@@ -385,10 +392,10 @@ _JS_LONG_FILE_SNAPSHOT = """
|
|||||||
const btn = document.querySelector('#sync-btn');
|
const btn = document.querySelector('#sync-btn');
|
||||||
if (!box || !textEl || !btn || box.hidden) return null;
|
if (!box || !textEl || !btn || box.hidden) return null;
|
||||||
const t = textEl.textContent;
|
const t = textEl.textContent;
|
||||||
const prefix = 'Syncing… ' + longPath + ' (';
|
const prefix = 'Syncing… ' + longPath + ' ';
|
||||||
if (!t.startsWith(prefix)) return null;
|
if (!t.startsWith(prefix)) return null;
|
||||||
const tail = t.slice(prefix.length);
|
const tail = t.slice(prefix.length);
|
||||||
if (!/^\\d+\\/3\\)( · chunk \\d+\\/\\d+)?$/.test(tail)) return null;
|
if (!/^2 remaining( · chunk \\d+\\/\\d+)?$/.test(tail)) return null;
|
||||||
const cs = (el) => getComputedStyle(el);
|
const cs = (el) => getComputedStyle(el);
|
||||||
return {
|
return {
|
||||||
boxText: t,
|
boxText: t,
|
||||||
@@ -407,8 +414,8 @@ _JS_LONG_FILE_SNAPSHOT = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
#: Test 3, wait B — the phase-129 chunk part IN THE BOX during the slow
|
#: Test 3, wait B — the phase-129 chunk part IN THE BOX during the slow
|
||||||
#: embed window: the box text is "Syncing… <full long path> (1/3) ·
|
#: embed window: the box text is "Syncing… <full long path>
|
||||||
#: chunk n/m" (the part is only present while an in-flight file's embed
|
#: 2 remaining · chunk n/m" (the part is only present while an in-flight file's embed
|
||||||
#: batch is in flight — the proxy's 1.5 s delay makes the ~15 s
|
#: batch is in flight — the proxy's 1.5 s delay makes the ~15 s
|
||||||
#: long-file window ≫ the 2 s poll, so the retry loop resolves
|
#: long-file window ≫ the 2 s poll, so the retry loop resolves
|
||||||
#: deterministically).
|
#: deterministically).
|
||||||
@@ -418,7 +425,7 @@ _JS_LONG_FILE_CHUNK_SNAPSHOT = """
|
|||||||
const textEl = document.querySelector('#sync-progress-text');
|
const textEl = document.querySelector('#sync-progress-text');
|
||||||
if (!box || !textEl || box.hidden) return null;
|
if (!box || !textEl || box.hidden) return null;
|
||||||
const t = textEl.textContent;
|
const t = textEl.textContent;
|
||||||
if (!t.startsWith('Syncing… ' + longPath + ' (1/3) · chunk ')) return null;
|
if (!t.startsWith('Syncing… ' + longPath + ' 2 remaining · chunk ')) return null;
|
||||||
const m = t.match(/· chunk (\\d+)\\/(\\d+)$/);
|
const m = t.match(/· chunk (\\d+)\\/(\\d+)$/);
|
||||||
if (!m) return null;
|
if (!m) return null;
|
||||||
return {
|
return {
|
||||||
@@ -453,7 +460,7 @@ _JS_MOBILE_SNAPSHOT = """
|
|||||||
if (!box || !textEl || !btn || !label || !icon || !container || !head) return null;
|
if (!box || !textEl || !btn || !label || !icon || !container || !head) return null;
|
||||||
if (box.hidden) return null;
|
if (box.hidden) return null;
|
||||||
const t = textEl.textContent;
|
const t = textEl.textContent;
|
||||||
if (!t.startsWith('Syncing… ' + longPath + ' (')) return null;
|
if (!t.startsWith('Syncing… ' + longPath + ' 2 remaining')) return null;
|
||||||
const cs = (el) => getComputedStyle(el);
|
const cs = (el) => getComputedStyle(el);
|
||||||
const rect = (el) => {
|
const rect = (el) => {
|
||||||
const b = el.getBoundingClientRect();
|
const b = el.getBoundingClientRect();
|
||||||
@@ -998,7 +1005,7 @@ def test_box_carries_the_full_untruncated_label(
|
|||||||
page: Page, app_url: str, db_ready: None
|
page: Page, app_url: str, db_ready: None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""During the in-flight window of the LONG-path file (poll until the
|
"""During the in-flight window of the LONG-path file (poll until the
|
||||||
box text is ``Syncing… <full long path> (1/3)`` — the slow proxy
|
box text is ``Syncing… <full long path> 2 remaining`` — the slow proxy
|
||||||
makes the ~15 s span deterministic): the box's text contains the
|
makes the ~15 s span deterministic): the box's text contains the
|
||||||
FULL label shape with the complete ``source/relative/path`` as a
|
FULL label shape with the complete ``source/relative/path`` as a
|
||||||
substring (the pre-phase ellipsis would have truncated it — the
|
substring (the pre-phase ellipsis would have truncated it — the
|
||||||
@@ -1022,7 +1029,7 @@ def test_box_carries_the_full_untruncated_label(
|
|||||||
|
|
||||||
# The FULL label shape — the complete long path IN the text (the
|
# The FULL label shape — the complete long path IN the text (the
|
||||||
# clipping trigger, untruncated).
|
# clipping trigger, untruncated).
|
||||||
expected_prefix = f"Syncing… {LONG_PATH} (1/3)"
|
expected_prefix = f"Syncing… {LONG_PATH} 2 remaining"
|
||||||
assert snap["boxText"].startswith(expected_prefix), snap["boxText"]
|
assert snap["boxText"].startswith(expected_prefix), snap["boxText"]
|
||||||
assert LONG_PATH in snap["boxText"], snap["boxText"]
|
assert LONG_PATH in snap["boxText"], snap["boxText"]
|
||||||
# The wrap engaged — no horizontal overflow (the whole point of the
|
# The wrap engaged — no horizontal overflow (the whole point of the
|
||||||
@@ -1084,7 +1091,9 @@ def test_mobile_box_full_width(page: Page, app_url: str, db_ready: None) -> None
|
|||||||
|
|
||||||
# The label wraps — the full long path is IN the box's text (the
|
# The label wraps — the full long path is IN the box's text (the
|
||||||
# old mobile-stuffing ellipsis is gone).
|
# old mobile-stuffing ellipsis is gone).
|
||||||
assert snap["boxText"].startswith(f"Syncing… {LONG_PATH} ("), snap["boxText"]
|
assert snap["boxText"].startswith(
|
||||||
|
f"Syncing… {LONG_PATH} 2 remaining"
|
||||||
|
), snap["boxText"]
|
||||||
assert LONG_PATH in snap["boxText"], snap["boxText"]
|
assert LONG_PATH in snap["boxText"], snap["boxText"]
|
||||||
# The wrap engaged — no horizontal clipping at the phone width.
|
# The wrap engaged — no horizontal clipping at the phone width.
|
||||||
assert snap["boxScrollWidth"] <= snap["boxClientWidth"], snap
|
assert snap["boxScrollWidth"] <= snap["boxClientWidth"], snap
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -705,8 +705,8 @@ def test_running_upload_line_unchanged(
|
|||||||
tarball_c: Path,
|
tarball_c: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""At the 202 moment the upload run is in flight: the button is
|
"""At the 202 moment the upload run is in flight: the button is
|
||||||
the BARE "Processing…" (phase 90, A2 — no file, no "(n/m)"
|
the BARE "Processing…" (phase 90, A2 — no file, no `N remaining`
|
||||||
counts, the title cleared) and ``#archive-upload-result`` is
|
count, the title cleared) and ``#archive-upload-result`` is
|
||||||
HIDDEN — the ready-for-sync line NEVER renders during the run
|
HIDDEN — the ready-for-sync line NEVER renders during the run
|
||||||
(the phase-90 contract: the line appears ONLY on settle). The
|
(the phase-90 contract: the line appears ONLY on settle). The
|
||||||
deterministic in-flight window: the 202 handler synchronously
|
deterministic in-flight window: the 202 handler synchronously
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ unpack → swap → row upsert continues server-side behind
|
|||||||
pattern; the phase-64 key set with ``current_file``/``files_done``/
|
pattern; the phase-64 key set with ``current_file``/``files_done``/
|
||||||
``files_total`` null/0/0 for the whole run — phase 90 A2), and the
|
``files_total`` null/0/0 for the whole run — phase 90 A2), and the
|
||||||
upload's UI processing state is the BARE "Processing…" (no file, no
|
upload's UI processing state is the BARE "Processing…" (no file, no
|
||||||
"(n/m)") until the no-count "Uploaded <name> (<stamp>) — press Sync
|
`N remaining` count) until the no-count "Uploaded <name> (<stamp>) — press Sync
|
||||||
sources to import it." result line lands (phase 90 A3 — the phase-154
|
sources to import it." result line lands (phase 90 A3 — the phase-154
|
||||||
completion stamp). The scan — with its live
|
completion stamp). The scan — with its live
|
||||||
file label — is the RAG page's **Sync sources** button's job, and the
|
file label — is the RAG page's **Sync sources** button's job, and the
|
||||||
@@ -47,6 +47,14 @@ is asserted at BOTH layers the task pins:
|
|||||||
the spinner) for the ``Syncing…`` prefix plus a file path (generous
|
the spinner) for the ``Syncing…`` prefix plus a file path (generous
|
||||||
timeout), which the page's own 2 s poll ticks render.
|
timeout), which the page's own 2 s poll ticks render.
|
||||||
|
|
||||||
|
Phase 193 (task 02) moves the running file-count part to the distance
|
||||||
|
to the end — ``N remaining`` with ``N = total - done`` (the owner's
|
||||||
|
"how many files are LEFT, not how many files out of the total"), the
|
||||||
|
upload job sharing the one builder and the shape. The box/title count
|
||||||
|
pins below therefore make the part optional: it is hidden on the FINAL
|
||||||
|
file, and this suite's 2 s poll can land exactly there — the pattern
|
||||||
|
still rejects the pre-phase ``(n/m)`` tail.
|
||||||
|
|
||||||
The upload archive is built in-test with Python's ``tarfile`` from
|
The upload archive is built in-test with Python's ``tarfile`` from
|
||||||
**25 small ``.md`` files** (``e2e-prog.tar.gz`` → source
|
**25 small ``.md`` files** (``e2e-prog.tar.gz`` → source
|
||||||
``e2e-prog``); the sync subjects are the uploaded row itself (the new
|
``e2e-prog``); the sync subjects are the uploaded row itself (the new
|
||||||
@@ -66,14 +74,16 @@ Contract under test:
|
|||||||
the 202; navigating to ``/sources.html`` shows **zero indexed
|
the 202; navigating to ``/sources.html`` shows **zero indexed
|
||||||
documents** (the upload unpacked + registered only) and the sync
|
documents** (the upload unpacked + registered only) and the sync
|
||||||
button settled idle with no error UI; clicking **Sync sources**
|
button settled idle with no error UI; clicking **Sync sources**
|
||||||
then imports the uploaded row with the LIVE "Syncing… <file> (n/m)"
|
then imports the uploaded row with the LIVE "Syncing… <file>
|
||||||
label (both layers — the phase-149 detail box carries the full
|
N remaining" label (phase 193 — the count part counts DOWN to the
|
||||||
|
end; both layers — the phase-149 detail box carries the full
|
||||||
string, the phase-129 pinned chunk part "· chunk n/m" rides the box
|
string, the phase-129 pinned chunk part "· chunk n/m" rides the box
|
||||||
+ title while a file's embed batch is in flight) and lands the
|
+ title while a file's embed batch is in flight) and lands the
|
||||||
counts ("N added", the catalog refreshes);
|
counts ("N added", the catalog refreshes);
|
||||||
* **upload processing (phase 90 A2)**: the upload button shows the
|
* **upload processing (phase 90 A2)**: the upload button shows the
|
||||||
BARE "Processing…" for the whole background run (no file, no
|
BARE "Processing…" for the whole background run (no file, no
|
||||||
"(n/m)", no title — proven across a held first status GET); every
|
`N remaining` count, no title — proven across a held first status
|
||||||
|
GET); every
|
||||||
running tick the recorder catches carries a null ``current_file``
|
running tick the recorder catches carries a null ``current_file``
|
||||||
and 0/0 counts; the terminal status is ``success`` with the no-count
|
and 0/0 counts; the terminal status is ``success`` with the no-count
|
||||||
``{"message": "uploaded"}`` detail and null/0/0 progress; the
|
``{"message": "uploaded"}`` detail and null/0/0 progress; the
|
||||||
@@ -81,7 +91,8 @@ Contract under test:
|
|||||||
* **sync live file (A4)**: a multi-file local source; clicking
|
* **sync live file (A4)**: a multi-file local source; clicking
|
||||||
**Sync sources** on ``/sources.html`` shows "Syncing…" (bare, the
|
**Sync sources** on ``/sources.html`` shows "Syncing…" (bare, the
|
||||||
pre-64 click state) in the detail box (phase 149 — the button is
|
pre-64 click state) in the detail box (phase 149 — the button is
|
||||||
the spinner) then "Syncing… <file> (n/m)" (both layers — the phase-
|
the spinner) then "Syncing… <file> N remaining" (phase 193; both
|
||||||
|
layers — the phase-
|
||||||
129 pinned chunk part "· chunk n/m" rides the box + title while a
|
129 pinned chunk part "· chunk n/m" rides the box + title while a
|
||||||
file's embed batch is in flight), then the pre-64 success settle —
|
file's embed batch is in flight), then the pre-64 success settle —
|
||||||
"Synced HH:MM" + the counts result line — preserved, plus the file
|
"Synced HH:MM" + the counts result line — preserved, plus the file
|
||||||
@@ -420,7 +431,7 @@ def _hold_first_status_fetch(page: Page, hold_s: float) -> None:
|
|||||||
AFTER the page's boot re-attach fetch, before the submit. The
|
AFTER the page's boot re-attach fetch, before the submit. The
|
||||||
poll's first tick fires 2 s after the 202; holding its fetch keeps
|
poll's first tick fires 2 s after the 202; holding its fetch keeps
|
||||||
the button in the in-run state long enough to assert the bare
|
the button in the in-run state long enough to assert the bare
|
||||||
"Processing…" label (no file, no "(n/m)") across the whole
|
"Processing…" label (no file, no `N remaining` count) across the whole
|
||||||
background run — phase 90's run settles in milliseconds, so without
|
background run — phase 90's run settles in milliseconds, so without
|
||||||
the hold the in-run window is only the 2 s pre-tick gap."""
|
the hold the in-run window is only the 2 s pre-tick gap."""
|
||||||
state = {"held": False}
|
state = {"held": False}
|
||||||
@@ -579,7 +590,7 @@ def test_upload_toast_then_navigate_away(
|
|||||||
documents** (phase 90 A1: the upload unpacked + registered only)
|
documents** (phase 90 A1: the upload unpacked + registered only)
|
||||||
and the sync button settled idle with no error UI; then click
|
and the sync button settled idle with no error UI; then click
|
||||||
**Sync sources** (the new leg, phase 90) → the LIVE
|
**Sync sources** (the new leg, phase 90) → the LIVE
|
||||||
"Syncing… <file> (n/m)" detail while the sync imports the uploaded
|
"Syncing… <file> N remaining" detail while the sync imports the uploaded
|
||||||
row (phase 149: the box under the button row — the button is the
|
row (phase 149: the box under the button row — the button is the
|
||||||
spinner — at both layers), then the success settle with the counts
|
spinner — at both layers), then the success settle with the counts
|
||||||
and the catalog refresh (the box hides)."""
|
and the catalog refresh (the box hides)."""
|
||||||
@@ -642,29 +653,32 @@ def test_upload_toast_then_navigate_away(
|
|||||||
# UI layer (phase 149 task 01): the RUNNING detail rides the
|
# UI layer (phase 149 task 01): the RUNNING detail rides the
|
||||||
# detail box — the page's 2 s poll tick reveals the live file in
|
# detail box — the page's 2 s poll tick reveals the live file in
|
||||||
# #sync-progress as the FULL untruncated string: "Syncing…
|
# #sync-progress as the FULL untruncated string: "Syncing…
|
||||||
# <source/rel/path> (n/m)" (+ " · chunk n/m" while a file's embed
|
# <source/rel/path> N remaining" (phase 193 — the count part
|
||||||
# batch is in flight — phase 129, batch granularity). The box is
|
# counts DOWN and hides on the FINAL file, so the part is optional
|
||||||
# the visible running surface — nothing is ellipsized away, the
|
# in this wait; the pre-phase "(n/m)" tail can no longer match)
|
||||||
# "(n/m)" count is IN the full string (the never-clipped pin),
|
# (+ " · chunk n/m" while a file's embed batch is in flight —
|
||||||
# and the button title carries the same full string (A4 — one
|
# phase 129, batch granularity). The box is the visible running
|
||||||
# source, two surfaces).
|
# surface — nothing is ellipsized away, the count is IN the full
|
||||||
|
# string (the never-clipped pin), and the button title carries the
|
||||||
|
# same full string (A4 — one source, two surfaces).
|
||||||
expect(page.locator("#sync-progress-text")).to_have_text(counts_line(
|
expect(page.locator("#sync-progress-text")).to_have_text(counts_line(
|
||||||
re.compile(
|
re.compile(
|
||||||
rf"^Syncing… {re.escape(UPLOAD_NAME)}/.+\.md \(\d+/{N_FILES}\)"
|
rf"^Syncing… {re.escape(UPLOAD_NAME)}/.+\.md"
|
||||||
r"(?: · chunk \d+/\d+)?$"
|
r"(?: [0-9]+ remaining)?(?: · chunk \d+/\d+)?$"
|
||||||
)),
|
)),
|
||||||
timeout=SETTLE_TIMEOUT_MS,
|
timeout=SETTLE_TIMEOUT_MS,
|
||||||
)
|
)
|
||||||
# The box is the VISIBLE running surface (the count's old
|
# The box is the VISIBLE running surface (the count's old
|
||||||
# "never clipped" pin now lives in its full string — the "(n/m)"
|
# "never clipped" pin now lives in its full string — the
|
||||||
# is in the text above).
|
# "N remaining" part is in the text above).
|
||||||
expect(page.locator("#sync-progress")).to_be_visible(
|
expect(page.locator("#sync-progress")).to_be_visible(
|
||||||
timeout=SETTLE_TIMEOUT_MS
|
timeout=SETTLE_TIMEOUT_MS
|
||||||
)
|
)
|
||||||
expect(btn).to_have_attribute(
|
expect(btn).to_have_attribute(
|
||||||
"title",
|
"title",
|
||||||
re.compile(
|
re.compile(
|
||||||
rf"^Syncing… {re.escape(UPLOAD_NAME)}/.+\.md \(\d+/{N_FILES}\)(?: · chunk \d+/\d+)?$"
|
rf"^Syncing… {re.escape(UPLOAD_NAME)}/.+\.md"
|
||||||
|
r"(?: [0-9]+ remaining)?(?: · chunk \d+/\d+)?$"
|
||||||
),
|
),
|
||||||
timeout=SETTLE_TIMEOUT_MS,
|
timeout=SETTLE_TIMEOUT_MS,
|
||||||
)
|
)
|
||||||
@@ -710,7 +724,7 @@ def test_upload_progress_is_bare(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Phase 90 A2: the upload's background run has NO file-level
|
"""Phase 90 A2: the upload's background run has NO file-level
|
||||||
progress. The button shows the BARE "Processing…" for the whole
|
progress. The button shows the BARE "Processing…" for the whole
|
||||||
run (no file, no "(n/m)", no title — proven across a held first
|
run (no file, no `N remaining` count, no title — proven across a held first
|
||||||
status GET); every running tick the recorder catches carries a
|
status GET); every running tick the recorder catches carries a
|
||||||
null ``current_file`` and 0/0 counts; the terminal status is
|
null ``current_file`` and 0/0 counts; the terminal status is
|
||||||
``success`` with the no-count ``{"message": "uploaded"}`` detail
|
``success`` with the no-count ``{"message": "uploaded"}`` detail
|
||||||
@@ -753,7 +767,7 @@ def test_upload_progress_is_bare(
|
|||||||
expect(btn).to_have_attribute("title", "")
|
expect(btn).to_have_attribute("title", "")
|
||||||
# …and it STAYS bare across the whole background run: the first
|
# …and it STAYS bare across the whole background run: the first
|
||||||
# status GET is held, so the settling tick is in flight — no file
|
# status GET is held, so the settling tick is in flight — no file
|
||||||
# and no "(n/m)" can have rendered.
|
# and no `N remaining` count can have rendered.
|
||||||
time.sleep(2.5)
|
time.sleep(2.5)
|
||||||
expect(btn).to_have_text("Processing…")
|
expect(btn).to_have_text("Processing…")
|
||||||
|
|
||||||
@@ -799,7 +813,7 @@ def test_sync_live_file_label(
|
|||||||
style): on ``/sources.html`` click **Sync sources** → the detail
|
style): on ``/sources.html`` click **Sync sources** → the detail
|
||||||
box (phase 149 — the running label left the pill; the button is
|
box (phase 149 — the running label left the pill; the button is
|
||||||
the spinner) shows "Syncing…" (bare, the pre-64 click state) then
|
the spinner) shows "Syncing…" (bare, the pre-64 click state) then
|
||||||
"Syncing… <file> (n/m)" while running (endpoint layer:
|
"Syncing… <file> N remaining" while running (endpoint layer:
|
||||||
``current_file`` non-null at running ticks; UI layer: the box
|
``current_file`` non-null at running ticks; UI layer: the box
|
||||||
poll), then the success settle with the counts result line (the
|
poll), then the success settle with the counts result line (the
|
||||||
box hides) — the pre-phase-64 sync UX's settle is preserved, plus
|
box hides) — the pre-phase-64 sync UX's settle is preserved, plus
|
||||||
@@ -839,22 +853,24 @@ def test_sync_live_file_label(
|
|||||||
# UI layer (phase 149 task 01): the RUNNING detail rides the
|
# UI layer (phase 149 task 01): the RUNNING detail rides the
|
||||||
# detail box — the page's 2 s poll tick reveals the live file in
|
# detail box — the page's 2 s poll tick reveals the live file in
|
||||||
# #sync-progress as the FULL untruncated string: "Syncing…
|
# #sync-progress as the FULL untruncated string: "Syncing…
|
||||||
# <source/rel/path> (n/m)" (+ " · chunk n/m" while a file's embed
|
# <source/rel/path> N remaining" (phase 193 — the count part
|
||||||
# batch is in flight — phase 129, batch granularity). The box is
|
# counts DOWN and hides on the FINAL file, so the part is optional
|
||||||
# the visible running surface — nothing is ellipsized away, the
|
# in this wait; the pre-phase "(n/m)" tail can no longer match)
|
||||||
# "(n/m)" count is IN the full string (the never-clipped pin),
|
# (+ " · chunk n/m" while a file's embed batch is in flight —
|
||||||
# and the button title carries the same full string (A4 — one
|
# phase 129, batch granularity). The box is the visible running
|
||||||
# source, two surfaces).
|
# surface — nothing is ellipsized away, the count is IN the full
|
||||||
|
# string (the never-clipped pin), and the button title carries the
|
||||||
|
# same full string (A4 — one source, two surfaces).
|
||||||
expect(page.locator("#sync-progress-text")).to_have_text(counts_line(
|
expect(page.locator("#sync-progress-text")).to_have_text(counts_line(
|
||||||
re.compile(
|
re.compile(
|
||||||
rf"^Syncing… {re.escape(SYNC_SOURCE_DIR)}/.+\.md \(\d+/{N_FILES}\)"
|
rf"^Syncing… {re.escape(SYNC_SOURCE_DIR)}/.+\.md"
|
||||||
r"(?: · chunk \d+/\d+)?$"
|
r"(?: [0-9]+ remaining)?(?: · chunk \d+/\d+)?$"
|
||||||
)),
|
)),
|
||||||
timeout=SETTLE_TIMEOUT_MS,
|
timeout=SETTLE_TIMEOUT_MS,
|
||||||
)
|
)
|
||||||
# The box is the VISIBLE running surface (the count's old
|
# The box is the VISIBLE running surface (the count's old
|
||||||
# "never clipped" pin now lives in its full string — the "(n/m)"
|
# "never clipped" pin now lives in its full string — the
|
||||||
# is in the text above).
|
# "N remaining" part is in the text above).
|
||||||
expect(page.locator("#sync-progress")).to_be_visible(
|
expect(page.locator("#sync-progress")).to_be_visible(
|
||||||
timeout=SETTLE_TIMEOUT_MS
|
timeout=SETTLE_TIMEOUT_MS
|
||||||
)
|
)
|
||||||
@@ -862,8 +878,8 @@ def test_sync_live_file_label(
|
|||||||
"title",
|
"title",
|
||||||
re.compile(
|
re.compile(
|
||||||
"^Syncing… "
|
"^Syncing… "
|
||||||
rf"{re.escape(SYNC_SOURCE_DIR)}/.+\.md \(\d+/{N_FILES}\)"
|
rf"{re.escape(SYNC_SOURCE_DIR)}/.+\.md"
|
||||||
r"(?: · chunk \d+/\d+)?$"
|
r"(?: [0-9]+ remaining)?(?: · chunk \d+/\d+)?$"
|
||||||
),
|
),
|
||||||
timeout=SETTLE_TIMEOUT_MS,
|
timeout=SETTLE_TIMEOUT_MS,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ check + import), so its background run settles in milliseconds and the
|
|||||||
racing a timer the suite polls the RAG page's settled sync UI
|
racing a timer the suite polls the RAG page's settled sync UI
|
||||||
(``#sync-label`` "Synced HH:MM" + the ``#sync-progress`` box's counts
|
(``#sync-label`` "Synced HH:MM" + the ``#sync-progress`` box's counts
|
||||||
line — phase 150: the box is the single sync text surface) with
|
line — phase 150: the box is the single sync text surface) with
|
||||||
generous timeouts — the live "Syncing… <file> (n/m)" label is the
|
generous timeouts — the live "Syncing… <file> N remaining" label is the
|
||||||
``test_sync_upload_progress.py`` suite's subject, not this one's.
|
``test_sync_upload_progress.py`` suite's subject, not this one's.
|
||||||
|
|
||||||
The archive is **built in-test** with Python's ``tarfile``:
|
The archive is **built in-test** with Python's ``tarfile``:
|
||||||
|
|||||||
@@ -79,8 +79,9 @@ its own (n/m) is its position, idle / settle). setSyncLabel(mainText,
|
|||||||
countText, chunkText) lands the part under the same present/absent
|
countText, chunkText) lands the part under the same present/absent
|
||||||
contract as the count part, and the UNTRUNCATED full label — main +
|
contract as the count part, and the UNTRUNCATED full label — main +
|
||||||
" " + count + " " + chunk — rides the button title + #sync-progress
|
" " + count + " " + chunk — rides the button title + #sync-progress
|
||||||
(A4 — the in-flight import reads "Syncing… <file> (2/3) · chunk
|
(A4 — the in-flight import reads "Syncing… <file> 1 remaining · chunk
|
||||||
14/38"). The part keeps the pinned mono CSS (flex 0 0 auto, nowrap,
|
14/38" — phase 193 counts the files DOWN). The part keeps the pinned
|
||||||
|
mono CSS (flex 0 0 auto, nowrap,
|
||||||
--mono, the rule right after .sync-label-count). A node harness (the
|
--mono, the rule right after .sync-label-count). A node harness (the
|
||||||
test-document-viewer pattern) behaviorally pins the rendered parts +
|
test-document-viewer pattern) behaviorally pins the rendered parts +
|
||||||
the full title across every label state.
|
the full title across every label state.
|
||||||
@@ -141,7 +142,8 @@ the 202/409 click stay bare). setSyncLabel(mainText, countText =
|
|||||||
"", chunkText = "", running = false, imageText = "") appends the
|
"", chunkText = "", running = false, imageText = "") appends the
|
||||||
image part LAST to the composed full string — so the box, the
|
image part LAST to the composed full string — so the box, the
|
||||||
button title, and the running aria-label all carry
|
button title, and the running aria-label all carry
|
||||||
"Syncing… <file> (2/3) · chunk 14/38 · image 3/15". The image part
|
"Syncing… <file> 1 remaining · chunk 14/38 · image 3/15" (phase 193).
|
||||||
|
The image part
|
||||||
gets NO span of its own (the running button is the spinner — its
|
gets NO span of its own (the running button is the spinner — its
|
||||||
parts are hidden, phase 149): it rides ONLY the composed string,
|
parts are hidden, phase 149): it rides ONLY the composed string,
|
||||||
while the chunk part keeps its .sync-label-chunk span; the two
|
while the chunk part keeps its .sync-label-chunk span; the two
|
||||||
@@ -189,6 +191,19 @@ app/ no-touch regression guard). The qualifier's byte-identity
|
|||||||
pinned in test_sync_button.py on the shared fixed clock; the four
|
pinned in test_sync_button.py on the shared fixed clock; the four
|
||||||
shapes and the fmtUploadStamp behavior (finished / started /
|
shapes and the fmtUploadStamp behavior (finished / started /
|
||||||
missing / garbage) ride the task-02 pins below.
|
missing / garbage) ride the task-02 pins below.
|
||||||
|
|
||||||
|
Phase 193 (task 01, D1) re-points the running file-count part: the
|
||||||
|
fmtSyncLabel COUNT part is the COUNT-DOWN `N remaining` (N =
|
||||||
|
total - done) — shown while N >= 1, HIDDEN at the final file (N == 0:
|
||||||
|
the main part names the file in flight — a "0 remaining" next to the
|
||||||
|
last file's name is the stale-looking edge the owner's complaint is
|
||||||
|
about), the total > 0 pre-walk guard KEPT, plain integer (no thousands
|
||||||
|
separator); the UPLOAD job shares the builder and the shape (A2). The
|
||||||
|
node harness rows that carried "(2/3)" now carry "1 remaining" (the
|
||||||
|
same 2/3 fixture — the part renders the distance to the end), the
|
||||||
|
harness gains the final-file row (the count part hidden), and the
|
||||||
|
summaries phase's own (n/m) folder position, the "· chunk n/m" /
|
||||||
|
"· image n/m" parts and the terminal counts line stay byte-identical.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -316,6 +331,19 @@ def _tick(js: str) -> str:
|
|||||||
return js[tick:end]
|
return js[tick:end]
|
||||||
|
|
||||||
|
|
||||||
|
def _doc_above(js: str, name: str) -> str:
|
||||||
|
"""The block comment immediately above `function <name>` (the
|
||||||
|
builder's docstring — _fn starts AT the keyword, so the lineage
|
||||||
|
paragraphs need this slice)."""
|
||||||
|
i = js.find(f"function {name}")
|
||||||
|
assert i != -1, f"{name} must be defined in sources.js"
|
||||||
|
start = js.rfind("/*", 0, i)
|
||||||
|
assert start != -1, f"{name} must carry a docstring"
|
||||||
|
end = js.find("*/", start)
|
||||||
|
assert start < end < i, f"{name}'s docstring must sit above the function"
|
||||||
|
return js[start:end]
|
||||||
|
|
||||||
|
|
||||||
# ---------- fmtSyncLabel: the live-file label PARTS contract ----------
|
# ---------- fmtSyncLabel: the live-file label PARTS contract ----------
|
||||||
|
|
||||||
|
|
||||||
@@ -328,9 +356,10 @@ def test_fmt_sync_label_returns_the_parts_contract() -> None:
|
|||||||
prefix shows during clone/pull, before any file is indexed — A4;
|
prefix shows during clone/pull, before any file is indexed — A4;
|
||||||
phase 90: the upload run never carries a file or counts, so its
|
phase 90: the upload run never carries a file or counts, so its
|
||||||
label is always the bare "Importing"); the COUNT part — only when
|
label is always the bare "Importing"); the COUNT part — only when
|
||||||
total > 0 (the import has started), NO join space (the space is
|
total > 0 (the import has started) — is the phase-193 COUNT-DOWN
|
||||||
the CSS gap between the parts — the full string's space is
|
`N remaining` (N = total - done), NO join space of its own (the
|
||||||
re-joined by setSyncLabel for the title / announcer, A4). File
|
space between the parts is the CSS gap — the full string's space
|
||||||
|
is re-joined by setSyncLabel for the title / announcer, A4). File
|
||||||
before counts in the full-string order."""
|
before counts in the full-string order."""
|
||||||
body = _fn(_js(), "fmtSyncLabel")
|
body = _fn(_js(), "fmtSyncLabel")
|
||||||
assert "function fmtSyncLabel(kind, currentFile, done, total)" in body
|
assert "function fmtSyncLabel(kind, currentFile, done, total)" in body
|
||||||
@@ -344,20 +373,95 @@ def test_fmt_sync_label_returns_the_parts_contract() -> None:
|
|||||||
# the MAIN part: the file appended only when present
|
# the MAIN part: the file appended only when present
|
||||||
assert "currentFile ?" in body
|
assert "currentFile ?" in body
|
||||||
assert "`${prefix} ${currentFile}`" in body
|
assert "`${prefix} ${currentFile}`" in body
|
||||||
# the COUNT part: only when the import has started (total > 0),
|
# the COUNT part (phase 193, D1): only when the import has
|
||||||
# no join space (the parts are joined by setSyncLabel, A4)
|
# started (the kept total > 0 pre-walk guard) AND the run is not
|
||||||
|
# on its final file (total - done > 0), no leading join space
|
||||||
|
# (the parts are joined by setSyncLabel, A4)
|
||||||
assert "total > 0" in body
|
assert "total > 0" in body
|
||||||
assert "`(${done}/${total})`" in body
|
assert "`${total - done} remaining`" in body
|
||||||
assert "` (${done}/${total})`" not in body, ("no space in the count part")
|
assert "(${done}/${total})" not in body, (
|
||||||
|
"the pre-phase (done/total) shape is gone (phase 193)"
|
||||||
|
)
|
||||||
i_file = body.find("currentFile ?")
|
i_file = body.find("currentFile ?")
|
||||||
i_counts = body.find("total > 0")
|
i_counts = body.find("total > 0")
|
||||||
assert -1 < i_file < i_counts, "the file lands before the counts"
|
assert -1 < i_file < i_counts, "the file lands before the counts"
|
||||||
# the parts return (the byte-identical full strings hold when
|
# the parts return (the full strings hold when setSyncLabel joins
|
||||||
# setSyncLabel joins them: "Syncing…", "Syncing… <file>",
|
# them: "Syncing…", "Syncing… <file>",
|
||||||
# "Syncing… <file> (n/m)", "Importing")
|
# "Syncing… <file> N remaining", "Importing")
|
||||||
assert "return { main, count }" in body
|
assert "return { main, count }" in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_fmt_sync_label_count_part_is_the_n_remaining_part() -> None:
|
||||||
|
"""Phase 193 (task 01, D1): the running file-count part counts
|
||||||
|
DOWN. The fmtSyncLabel count expression is pinned VERBATIM — the
|
||||||
|
kept `total > 0` pre-walk guard (phase 64: no count part before
|
||||||
|
the walk knows the total) AND the new `total - done > 0` guard
|
||||||
|
(the part hides at the final file, ASSUMPTION A1) around the
|
||||||
|
plain-integer `${total - done} remaining` shape (no thousands
|
||||||
|
separator — the existing count part's house style). The docstring
|
||||||
|
names the new part (N = total - done, shown while N >= 1), the
|
||||||
|
hidden-at-the-final-file edge (N == 0 — the main part names the
|
||||||
|
file in flight; a "0 remaining" next to the last file's name is
|
||||||
|
the stale-looking edge), and the upload job sharing the builder
|
||||||
|
and the shape (A2)."""
|
||||||
|
js = _js()
|
||||||
|
body = _fn(js, "fmtSyncLabel")
|
||||||
|
# the exact expression, byte-for-byte (both guards + the shape +
|
||||||
|
# the empty-string else)
|
||||||
|
assert (
|
||||||
|
"const count = total > 0 && total - done > 0"
|
||||||
|
" ? `${total - done} remaining` : \"\";"
|
||||||
|
) in body, "the count expression is the D1 shape"
|
||||||
|
assert "total > 0 &&" in body, "the total > 0 pre-walk guard stays"
|
||||||
|
assert "total - done > 0" in body, (
|
||||||
|
"the part hides at the final file (N == 0, A1)"
|
||||||
|
)
|
||||||
|
# plain integer — no separator/format helper joins the number
|
||||||
|
assert "toLocaleString" not in body and ".toFixed" not in body, (
|
||||||
|
"plain integer, no thousands separator (the house label style)"
|
||||||
|
)
|
||||||
|
doc = _doc_above(js, "fmtSyncLabel")
|
||||||
|
assert "`N remaining`" in doc and "N = total - done" in doc, (
|
||||||
|
"the docstring describes the new part"
|
||||||
|
)
|
||||||
|
assert "N >= 1" in doc, "shown while N >= 1 (A1)"
|
||||||
|
assert "HIDDEN at the final file" in doc and "N == 0" in doc, (
|
||||||
|
"the hidden-at-final-file edge is named in the docstring (A1)"
|
||||||
|
)
|
||||||
|
assert "thousands separator" in doc, "the plain-integer clause is named"
|
||||||
|
assert "pre-walk" in doc, "the phase-64 total > 0 contract is named"
|
||||||
|
assert "shares this builder" in doc, (
|
||||||
|
"the upload job sharing the builder and the shape is named (A2)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_remaining_part_leaves_the_other_parts_byte_identical() -> None:
|
||||||
|
"""Phase 193 (task 01): the COUNT-DOWN moved exactly ONE part.
|
||||||
|
The fmtSyncPhaseLabel import fall-through still builds the
|
||||||
|
phase-129 "· chunk n/m" and the phase-152 "· image n/m" parts
|
||||||
|
from the status's own fields, the summaries branch still emits
|
||||||
|
its FOLDER position `(${summaries_done}/${summaries_total})`,
|
||||||
|
the bare fmtSyncLabel builder stays free of all three (each part
|
||||||
|
lives only in its own builder), and the terminal counts line
|
||||||
|
(fmtSyncResult — a different surface) never grows the word."""
|
||||||
|
js = _js()
|
||||||
|
phase = _fn(js, "fmtSyncPhaseLabel")
|
||||||
|
assert "`· chunk ${status.current_chunk}/${status.chunks_total}`" in phase
|
||||||
|
assert "`· image ${status.images_done}/${status.images_total}`" in phase
|
||||||
|
assert (
|
||||||
|
"count: `(${status.summaries_done}/${status.summaries_total})`"
|
||||||
|
) in phase, "the summaries folder position keeps its (n/m) shape"
|
||||||
|
bare = _fn(js, "fmtSyncLabel")
|
||||||
|
for part in ("chunk", "image", "summaries"):
|
||||||
|
assert part not in bare, (
|
||||||
|
f"fmtSyncLabel stays {part}-free — the part lives only in its own builder"
|
||||||
|
)
|
||||||
|
assert "remaining" not in _fn(js, "fmtSyncResult"), (
|
||||||
|
"the terminal result line is untouched (the running count part"
|
||||||
|
" is the one change)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_set_sync_label_is_the_single_parts_writer() -> None:
|
def test_set_sync_label_is_the_single_parts_writer() -> None:
|
||||||
"""setSyncLabel(mainText, countText = "", chunkText = "",
|
"""setSyncLabel(mainText, countText = "", chunkText = "",
|
||||||
running = false, imageText = "") — Phase 128 (task 01) + Phase
|
running = false, imageText = "") — Phase 128 (task 01) + Phase
|
||||||
@@ -374,7 +478,7 @@ def test_set_sync_label_is_the_single_parts_writer() -> None:
|
|||||||
(countText ? " " + countText : "") + (chunkText ? " " + chunkText
|
(countText ? " " + countText : "") + (chunkText ? " " + chunkText
|
||||||
: "") + (imageText ? " " + imageText : "") — rides the button
|
: "") + (imageText ? " " + imageText : "") — rides the button
|
||||||
title (A4 — the screen reader hears the
|
title (A4 — the screen reader hears the
|
||||||
whole "Syncing… <file> (2/3) · chunk 14/38 · image 3/15" there,
|
whole "Syncing… <file> 1 remaining · chunk 14/38 · image 3/15" there,
|
||||||
and in the box — see below). The image part (phase 152) gets NO
|
and in the box — see below). The image part (phase 152) gets NO
|
||||||
span of its own (the running button is the spinner — its parts
|
span of its own (the running button is the spinner — its parts
|
||||||
are hidden, phase 149): it rides only the composed full string.
|
are hidden, phase 149): it rides only the composed full string.
|
||||||
@@ -868,6 +972,15 @@ enterSyncRunningState("sync", "src/a/b.md", 2, 3, {
|
|||||||
current_chunk: 0, chunks_total: 0,
|
current_chunk: 0, chunks_total: 0,
|
||||||
});
|
});
|
||||||
show("import-prebatch");
|
show("import-prebatch");
|
||||||
|
// 2b. Phase 193 (task 01, A1): the FINAL file (files_done ==
|
||||||
|
// files_total) — the count part HIDES (the main part names the
|
||||||
|
// file in flight; a "0 remaining" next to it is the stale-looking
|
||||||
|
// edge):
|
||||||
|
enterSyncRunningState("sync", "src/a/b.md", 3, 3, {
|
||||||
|
phase: "import", current_file: "src/a/b.md", files_done: 3, files_total: 3,
|
||||||
|
current_chunk: 0, chunks_total: 0,
|
||||||
|
});
|
||||||
|
show("import-final");
|
||||||
// 3. the prelude (null phase, no file yet):
|
// 3. the prelude (null phase, no file yet):
|
||||||
enterSyncRunningState("sync", null, 0, 0, {
|
enterSyncRunningState("sync", null, 0, 0, {
|
||||||
phase: null, current_file: null, files_done: 0, files_total: 0,
|
phase: null, current_file: null, files_done: 0, files_total: 0,
|
||||||
@@ -957,9 +1070,13 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
|
|||||||
* import, batch in flight (chunks_total 38 > 0): the chunk part
|
* import, batch in flight (chunks_total 38 > 0): the chunk part
|
||||||
shows "· chunk 14/38" and the UNTRUNCATED title + #sync-progress
|
shows "· chunk 14/38" and the UNTRUNCATED title + #sync-progress
|
||||||
(the sole live region) read exactly
|
(the sole live region) read exactly
|
||||||
"Syncing… src/a/b.md (2/3) · chunk 14/38" (A4);
|
"Syncing… src/a/b.md 1 remaining · chunk 14/38" (A4 — phase
|
||||||
|
193: the 2/3 fixture now renders the distance to the end);
|
||||||
* import before the first batch (0/0): the chunk part hides and
|
* import before the first batch (0/0): the chunk part hides and
|
||||||
the label stays "Syncing… src/a/b.md (2/3)";
|
the label stays "Syncing… src/a/b.md 1 remaining";
|
||||||
|
* import on the FINAL file (phase 193, A1: files_done ==
|
||||||
|
files_total): the COUNT part hides (no "0 remaining") — the
|
||||||
|
main part alone names the file in flight;
|
||||||
* every OTHER state — prelude (null phase), the 202/409 click (no
|
* every OTHER state — prelude (null phase), the 202/409 click (no
|
||||||
status), the upload run (no status, D2), overview, summaries (its
|
status), the upload run (no status, D2), overview, summaries (its
|
||||||
own (n/m) is its position), the idle settle — leaves the chunk
|
own (n/m) is its position), the idle settle — leaves the chunk
|
||||||
@@ -982,24 +1099,39 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
|
|||||||
# untruncated label rides the title + the announcer (A4).
|
# untruncated label rides the title + the announcer (A4).
|
||||||
row = by_tag["import-inflight"]
|
row = by_tag["import-inflight"]
|
||||||
assert row[3] == "Syncing… src/a/b.md", row
|
assert row[3] == "Syncing… src/a/b.md", row
|
||||||
assert row[4] == "(2/3)" and row[5] is False, row
|
assert row[4] == "1 remaining" and row[5] is False, (
|
||||||
|
"phase 193 (D1): the count part is the count-DOWN (3 − 2 = 1)"
|
||||||
|
)
|
||||||
assert row[6] == "· chunk 14/38" and row[7] is False, (
|
assert row[6] == "· chunk 14/38" and row[7] is False, (
|
||||||
"the chunk part shows the batch position (1-based first chunk / file total)"
|
"the chunk part shows the batch position (1-based first chunk / file total)"
|
||||||
)
|
)
|
||||||
assert row[1] == "Syncing… src/a/b.md (2/3) · chunk 14/38", row[1]
|
assert row[1] == "Syncing… src/a/b.md 1 remaining · chunk 14/38", row[1]
|
||||||
assert row[2] == row[1], "the announcer hears the whole thing (A4)"
|
assert row[2] == row[1], "the announcer hears the whole thing (A4)"
|
||||||
|
|
||||||
# 2. the import before the first batch (0/0) — the part hides, the
|
# 2. the import before the first batch (0/0) — the chunk part
|
||||||
# label is the byte-identical phase-64 string.
|
# hides, the label keeps its phase-64 shape with the phase-193
|
||||||
|
# count part.
|
||||||
row = by_tag["import-prebatch"]
|
row = by_tag["import-prebatch"]
|
||||||
assert row[1] == "Syncing… src/a/b.md (2/3)", row[1]
|
assert row[1] == "Syncing… src/a/b.md 1 remaining", row[1]
|
||||||
assert row[2] == row[1]
|
assert row[2] == row[1]
|
||||||
assert row[6] == "" and row[7] is True, row
|
assert row[6] == "" and row[7] is True, row
|
||||||
|
|
||||||
|
# 2b. the FINAL file (N == 0) — the count part HIDES (phase 193,
|
||||||
|
# A1): no "0 remaining", the main part names the file, and
|
||||||
|
# the A4 full label is just the main part.
|
||||||
|
row = by_tag["import-final"]
|
||||||
|
assert row[3] == "Syncing… src/a/b.md", row
|
||||||
|
assert row[4] == "" and row[5] is True, (
|
||||||
|
"the count part hides at the final file (total - done == 0)"
|
||||||
|
)
|
||||||
|
assert row[1] == "Syncing… src/a/b.md", row[1]
|
||||||
|
assert row[2] == row[1]
|
||||||
|
|
||||||
# 3. every other RUNNING state — the chunk part is hidden +
|
# 3. every other RUNNING state — the chunk part is hidden +
|
||||||
# empty, the title is the byte-identical pre-phase-129 label,
|
# empty, the title is the byte-identical pre-phase-129 label,
|
||||||
# and the announcer (the box) hears the whole label (A4).
|
# and the announcer (the box) hears the whole label (A4).
|
||||||
for tag, expected_title in (
|
for tag, expected_title in (
|
||||||
|
("import-final", "Syncing… src/a/b.md"),
|
||||||
("prelude", "Syncing…"),
|
("prelude", "Syncing…"),
|
||||||
("click", "Syncing…"),
|
("click", "Syncing…"),
|
||||||
("upload", "Importing"),
|
("upload", "Importing"),
|
||||||
@@ -1029,7 +1161,15 @@ test-document-viewer pattern). The pinned "· chunk n/m" part (the
|
|||||||
# upload job is NOT cancellable in this phase) and hidden +
|
# upload job is NOT cancellable in this phase) and hidden +
|
||||||
# disabled after the real settle (settleSyncButton always hides
|
# disabled after the real settle (settleSyncButton always hides
|
||||||
# it — every terminal + idle passes through it).
|
# it — every terminal + idle passes through it).
|
||||||
for tag in ("import-inflight", "import-prebatch", "prelude", "click", "overview", "summaries"):
|
for tag in (
|
||||||
|
"import-inflight",
|
||||||
|
"import-prebatch",
|
||||||
|
"import-final",
|
||||||
|
"prelude",
|
||||||
|
"click",
|
||||||
|
"overview",
|
||||||
|
"summaries",
|
||||||
|
):
|
||||||
row = by_tag[tag]
|
row = by_tag[tag]
|
||||||
assert row[8] is False and row[9] is False, (
|
assert row[8] is False and row[9] is False, (
|
||||||
f"{tag}: the Cancel control must be shown + enabled while a sync run is in flight"
|
f"{tag}: the Cancel control must be shown + enabled while a sync run is in flight"
|
||||||
@@ -1053,14 +1193,15 @@ def test_image_part_renders_only_in_the_import_phase_under_node() -> None:
|
|||||||
WITH the last chunk position still on the status (125/126):
|
WITH the last chunk position still on the status (125/126):
|
||||||
the composed full string carries BOTH parts — the box, the
|
the composed full string carries BOTH parts — the box, the
|
||||||
button title, and the running aria-label all read exactly
|
button title, and the running aria-label all read exactly
|
||||||
"Syncing… src/a/b.md (2/3) · chunk 125/126 · image 3/15"
|
"Syncing… src/a/b.md 1 remaining · chunk 125/126 · image 3/15"
|
||||||
(the owner's stuck label, now moving); the chunk part KEEPS
|
(the owner's stuck label, now moving; phase 193: the count part
|
||||||
|
is the count-DOWN); the chunk part KEEPS
|
||||||
its span ("· chunk 125/126" visible) and the image part gets
|
its span ("· chunk 125/126" visible) and the image part gets
|
||||||
NO span of its own (it rides the composed string only — the
|
NO span of its own (it rides the composed string only — the
|
||||||
running button is the spinner, phase 149);
|
running button is the spinner, phase 149);
|
||||||
* import, a purely pictorial PDF (the chunk position never left
|
* import, a purely pictorial PDF (the chunk position never left
|
||||||
0/0) in its vision span: the image part shows ALONE —
|
0/0) in its vision span: the image part shows ALONE —
|
||||||
"Syncing… src/a/b.md (2/3) · image 7/7" — and the chunk part
|
"Syncing… src/a/b.md 1 remaining · image 7/7" — and the chunk part
|
||||||
stays hidden + empty (the two parts are distinct and each
|
stays hidden + empty (the two parts are distinct and each
|
||||||
disappears independently on its own 0/0);
|
disappears independently on its own 0/0);
|
||||||
* every OTHER running state (the pre-batch import, the prelude,
|
* every OTHER running state (the pre-batch import, the prelude,
|
||||||
@@ -1082,7 +1223,7 @@ def test_image_part_renders_only_in_the_import_phase_under_node() -> None:
|
|||||||
# (the image part LAST, after the chunk part), on the box, the
|
# (the image part LAST, after the chunk part), on the box, the
|
||||||
# title, and the running aria-label (A4) — the chunk span
|
# title, and the running aria-label (A4) — the chunk span
|
||||||
# keeps its own part.
|
# keeps its own part.
|
||||||
full = "Syncing… src/a/b.md (2/3) · chunk 125/126 · image 3/15"
|
full = "Syncing… src/a/b.md 1 remaining · chunk 125/126 · image 3/15"
|
||||||
row = by_tag["import-image-span"]
|
row = by_tag["import-image-span"]
|
||||||
assert row[1] == full, row[1]
|
assert row[1] == full, row[1]
|
||||||
assert row[2] == full, "the announcer (the box) hears the whole thing (A4)"
|
assert row[2] == full, "the announcer (the box) hears the whole thing (A4)"
|
||||||
@@ -1095,7 +1236,7 @@ def test_image_part_renders_only_in_the_import_phase_under_node() -> None:
|
|||||||
# 1c. the image part alone (the chunk position never left 0/0 —
|
# 1c. the image part alone (the chunk position never left 0/0 —
|
||||||
# the two parts are independent).
|
# the two parts are independent).
|
||||||
row = by_tag["import-image-only"]
|
row = by_tag["import-image-only"]
|
||||||
assert row[1] == "Syncing… src/a/b.md (2/3) · image 7/7", row[1]
|
assert row[1] == "Syncing… src/a/b.md 1 remaining · image 7/7", row[1]
|
||||||
assert row[2] == row[1] and row[14] == row[1], row
|
assert row[2] == row[1] and row[14] == row[1], row
|
||||||
assert row[6] == "" and row[7] is True, (
|
assert row[6] == "" and row[7] is True, (
|
||||||
"the chunk part stays hidden + empty (its own 0/0)"
|
"the chunk part stays hidden + empty (its own 0/0)"
|
||||||
@@ -1106,8 +1247,9 @@ def test_image_part_renders_only_in_the_import_phase_under_node() -> None:
|
|||||||
# hides on its own 0/0 — the overview/summaries transitions
|
# hides on its own 0/0 — the overview/summaries transitions
|
||||||
# clear the position server-side, the bare builders carry none).
|
# clear the position server-side, the bare builders carry none).
|
||||||
for tag, expected_title in (
|
for tag, expected_title in (
|
||||||
("import-inflight", "Syncing… src/a/b.md (2/3) · chunk 14/38"),
|
("import-inflight", "Syncing… src/a/b.md 1 remaining · chunk 14/38"),
|
||||||
("import-prebatch", "Syncing… src/a/b.md (2/3)"),
|
("import-prebatch", "Syncing… src/a/b.md 1 remaining"),
|
||||||
|
("import-final", "Syncing… src/a/b.md"),
|
||||||
("prelude", "Syncing…"),
|
("prelude", "Syncing…"),
|
||||||
("click", "Syncing…"),
|
("click", "Syncing…"),
|
||||||
("upload", "Importing"),
|
("upload", "Importing"),
|
||||||
@@ -1176,6 +1318,7 @@ def test_progress_box_and_running_button_contract_under_node() -> None:
|
|||||||
for tag in (
|
for tag in (
|
||||||
"import-inflight",
|
"import-inflight",
|
||||||
"import-prebatch",
|
"import-prebatch",
|
||||||
|
"import-final",
|
||||||
"prelude",
|
"prelude",
|
||||||
"click",
|
"click",
|
||||||
"upload",
|
"upload",
|
||||||
|
|||||||
@@ -268,8 +268,9 @@ def test_sync_button_has_icon_and_label_parts() -> None:
|
|||||||
Phase 128 (task 01): #sync-label is PARTS — the MAIN span
|
Phase 128 (task 01): #sync-label is PARTS — the MAIN span
|
||||||
(.sync-label-main) carries the idle text (the pre-JS label renders
|
(.sync-label-main) carries the idle text (the pre-JS label renders
|
||||||
from the static markup; the part CSS may ellipsize it), the
|
from the static markup; the part CSS may ellipsize it), the
|
||||||
COUNT span (.sync-label-count — the pinned "(n/m)" position,
|
COUNT span (.sync-label-count — the pinned phase-193
|
||||||
never clipped) ships hidden + empty (there is no count at idle),
|
"N remaining" position, never clipped) ships hidden + empty
|
||||||
|
(there is no count at idle),
|
||||||
and — phase 129 (task 04) — the CHUNK span (.sync-label-chunk —
|
and — phase 129 (task 04) — the CHUNK span (.sync-label-chunk —
|
||||||
the pinned "· chunk n/m" embed-batch position) ships hidden +
|
the pinned "· chunk n/m" embed-batch position) ships hidden +
|
||||||
empty too (no chunk position at idle — the part renders only
|
empty too (no chunk position at idle — the part renders only
|
||||||
@@ -516,7 +517,7 @@ def test_fmt_sync_phase_label_image_part_is_pinned_source_level() -> None:
|
|||||||
`running` flag);
|
`running` flag);
|
||||||
* ``setSyncLabel``'s composed ``full`` string appends the image
|
* ``setSyncLabel``'s composed ``full`` string appends the image
|
||||||
part LAST (after the chunk part) — so a status carrying both
|
part LAST (after the chunk part) — so a status carrying both
|
||||||
positions reads exactly "Syncing… <file> (2540/2540) · chunk
|
positions reads exactly "Syncing… <file> 1 remaining · chunk
|
||||||
125/126 · image 3/15" on the box, the title, and the running
|
125/126 · image 3/15" on the box, the title, and the running
|
||||||
aria-label (byte-for-byte; the docstring's worked example
|
aria-label (byte-for-byte; the docstring's worked example
|
||||||
carries the shape).
|
carries the shape).
|
||||||
|
|||||||
Reference in New Issue
Block a user