feat(sources): removing a source deletes its files and index entries behind a confirmation modal
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
All gates green. Final state verified:
|
||||
|
||||
**Report — Task 01: Full-Removal Backend (phase 69)**
|
||||
|
||||
**Implemented**
|
||||
- `app/rag/source_removal.py` (new, no FastAPI): `resolve_source_name` (reuses `repo_name` / sync's local-path expression), `managed_dir_for` (git → `sources_dir/<repo>`, local → only `upload_dir` itself or nested under it via resolved-path containment — prefix-siblings and escaping symlinks → `None`), `remove_managed_dir` (never raises; `OSError` → `logger.exception` + `False`), `has_sibling` (Python-side name comparison, self-excluded)
|
||||
- `app/api/git_sources.py::delete_git_source` rewired (now `async def`): sibling guard → row-only; DB-first (docs + row, one commit, cascade drops chunks/embeddings); disk second (app-managed dir only; foreign local dirs skipped); when pruned > 0 → best-effort `regenerate_overview` then exactly one `sources_version` bump in a short-lived session; one `source removed: …` INFO line; 204/404/422 contract unchanged; module + endpoint docstrings updated (Sync `prune=True` = upstream churn only)
|
||||
- New tests: `tests/unit/test_source_removal.py` (27), `tests/integration/test_source_removal_api.py` (8 — full matrix incl. sibling guard, containment edge, LLM-outage best-effort, bump gates, log-line pins); hardened `test_git_sources_api.py::test_delete_removes_row_and_falls_back_to_env` (tmp `sources_dir`/`upload_dir`, KB-table ownership — no rmtree aimed at real `~/bor-sources`)
|
||||
- Fixed pre-existing defect (phase 68 commit `189aa92`): `tests/unit/test_agent.py` still pinned the old tool-description text → 4 pins updated to the intended shortened wording
|
||||
|
||||
**Gates** (all run this session)
|
||||
- `uv run pytest tests/unit/test_source_removal.py tests/integration/test_source_removal_api.py -v --no-cov` → 35 passed
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` → 1352 passed, TOTAL **99%** (source_removal.py 100%, git_sources.py 100%)
|
||||
- `uv run ruff check . && uv run pyright` → clean / 0 errors
|
||||
- Regression: `tests/e2e/test_git_sources_admin.py` 6 passed; `tests/e2e/test_archive_upload_sources.py` 5 passed (in isolation)
|
||||
|
||||
**Decisions**: no commit (task 03 owns the phase commit + phase-dir move); `has_sibling` unit-tested via a duck-typed session + `cast` (house pattern, no SQLite for pgvector/JSONB models).
|
||||
|
||||
**Next pending task**: `.agent/phases/todo/69_source_removal_cleanup/02_confirmation_modal.md`
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
........................................................................ [ 5%]
|
||||
........................................................................ [ 10%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 21%]
|
||||
........................................................................ [ 26%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 37%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 47%]
|
||||
........................................................................ [ 53%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 63%]
|
||||
........................................................................ [ 69%]
|
||||
........................................................................ [ 74%]
|
||||
........................................................................ [ 79%]
|
||||
........................................................................ [ 85%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 95%]
|
||||
........................................................ [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/auth.py 22 0 100%
|
||||
app/api/chat.py 153 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 7 0 100%
|
||||
app/api/doc_drafts.py 93 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 212 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 8 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/config.py 126 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 20 0 100%
|
||||
app/core/caching.py 108 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 52 0 100%
|
||||
app/models.py 86 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 120 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 180 3 98%
|
||||
app/rag/llm.py 213 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 65 0 100%
|
||||
app/rag/retriever.py 94 3 97%
|
||||
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 24 0 100%
|
||||
app/schemas.py 205 0 100%
|
||||
-----------------------------------------------
|
||||
TOTAL 2707 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Everything is green and verified. Final report:
|
||||
|
||||
## Task 02 complete — confirmation modal on the Git sources page
|
||||
|
||||
**Implemented**
|
||||
- `frontend/git-sources.html`: static `#remove-confirm-dialog` (`role="alertdialog"`, `aria-modal`, labelled/describedby, hidden) inside `#git-sources-content` — locked title/copy verbatim, `#remove-confirm-source` `<code>`, `role="alert"` error line, Cancel + "Remove source" `type="button"`; `#git-sources-hint` rewritten to the total-removal contract
|
||||
- `frontend/assets/git-sources.js`: `window.confirm` flow replaced — `openRemoveConfirm` (textContent-only source, makeRow's `value` expr, focus on Cancel, trigger recorded), Escape/Cancel/backdrop cancel (no request, focus return, in-flight no-op), `confirmRemove` §7.4 lifecycle ("Removing…", both buttons disabled, in-modal error on non-2xx, fixed reachable? line, re-enable in finally, success → close/announce/`loadSources()`); module docstring "remove" bullet + scope boundary updated; dead per-row error span retired
|
||||
- `frontend/assets/styles.css`: `.remove-confirm-*` modal on phase-08 tokens only (46rem cap, no blur, ≥44px buttons, err-token destructive pair 9.3:1/5.2:1, global 3px focus-visible); dead `.git-source-row-error` CSS removed
|
||||
- `tests/unit/test_remove_confirm_modal.py` (new, 12 tests, house source-parsing pattern): no `window.confirm` anywhere in `frontend/`, dialog markup/aria pins, full JS lifecycle order pins, stale "next sync" copy gone, new hint + docstring copy present, CSS token/contrast pins
|
||||
|
||||
**Results**
|
||||
- `uv run pytest tests/unit/test_remove_confirm_modal.py -v --no-cov` → 12 passed
|
||||
- `uv run pytest` (unit+integration) → green (exit 0); `uv run pytest --cov=app` TOTAL **99%** (>90%)
|
||||
- `uv run ruff check . && uv run pyright` → clean; `rg window.confirm frontend/` → no matches
|
||||
- Bonus: real-browser Playwright smoke (temp DB, since deleted; owner's DB restored) verified Escape/backdrop/confirm paths, focus contract, 204 → row + on-disk checkout gone
|
||||
|
||||
**Decisions**: backdrop implemented as its own `aria-hidden` element with a direct cancel listener (doc-modal overlay contract); small Tab/Shift+Tab cycle added so `aria-modal` holds for keyboard users; task's `.tuning-ne`/`.steering-ne` refs map to the actual `.tuning-delete`/`.steering-delete` err-pair convention.
|
||||
|
||||
**Next pending task**: `.agent/phases/todo/69_source_removal_cleanup/03_e2e_and_commit.md` (note: `tests/e2e/test_git_sources_admin.py` test 4 still pins the old dialog flow — red by design until task 03 rewrites it).
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
........................................................................ [ 5%]
|
||||
........................................................................ [ 10%]
|
||||
........................................................................ [ 15%]
|
||||
........................................................................ [ 21%]
|
||||
........................................................................ [ 26%]
|
||||
........................................................................ [ 31%]
|
||||
........................................................................ [ 36%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 47%]
|
||||
........................................................................ [ 52%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 63%]
|
||||
........................................................................ [ 68%]
|
||||
........................................................................ [ 73%]
|
||||
........................................................................ [ 79%]
|
||||
........................................................................ [ 84%]
|
||||
........................................................................ [ 89%]
|
||||
........................................................................ [ 95%]
|
||||
.................................................................... [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/auth.py 22 0 100%
|
||||
app/api/chat.py 153 0 100%
|
||||
app/api/chats.py 110 0 100%
|
||||
app/api/config.py 7 0 100%
|
||||
app/api/doc_drafts.py 93 0 100%
|
||||
app/api/docs.py 50 0 100%
|
||||
app/api/git_sources.py 212 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 8 0 100%
|
||||
app/api/sync.py 101 0 100%
|
||||
app/config.py 126 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 20 0 100%
|
||||
app/core/caching.py 108 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/docs_push.py 39 0 100%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 52 0 100%
|
||||
app/models.py 86 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 120 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 180 3 98%
|
||||
app/rag/llm.py 213 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 65 0 100%
|
||||
app/rag/retriever.py 94 3 97%
|
||||
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 24 0 100%
|
||||
app/schemas.py 205 0 100%
|
||||
-----------------------------------------------
|
||||
TOTAL 2707 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
Reference in New Issue
Block a user