From 990c8adf13db7c46f709b74b7c8f8b92a839a5ac Mon Sep 17 00:00:00 2001 From: ducoterra Date: Tue, 15 Sep 2026 05:35:05 -0400 Subject: [PATCH] phase: 115_doc_draft_discard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All green — this was the final verification pass; everything from the four completed tasks was already in the working tree and verified. **Phase 115 — Doc drafts: Discard + DELETE route + title fix — verification report** - Verified all 4 task deliverables present: DELETE route (`app/api/doc_drafts.py`), Discard UI (`doc-edit.html` + `doc-edit.js` + `.discard-draft` CSS), title fix (`defaultDocTitle(wrap)` pairing + `saveAsDoc` call site), and all test pins (integration, frontend unit, E2E). No code changes needed. - **Completion criteria:** 1. ✅ Orphaned draft discardable from edit screen; row gone — `test_delete_removes_row_and_invalidates_token` (204 → GET 404), unknown-token 404, admin-gate 403 on all routes, E2E `test_discard_draft_from_edit_screen` all pass. 2. ✅ Title after retry redo = redone answer's own question — E2E `test_save_title_is_the_redo_question_after_retry` passes. 3. ✅ Push flow byte-identical — `git diff` shows only the new DELETE route + module docstring; all 7 existing push tests green. 4. ✅ `uv run pytest --cov=app` → **2457 passed**, app coverage **99%** (>90%); `uv run pytest tests/e2e/test_save_doc_session.py -v --no-cov` → **4 passed**; `uv run ruff check .` → clean; `uv run pyright` → 0 errors. 5. ⏳ Commit + phase-dir move left to the harness (per executor rules, no `git` run; all changes left in the working tree). - No defects found; no deviations. - Next pending phase: none in `todo/` other than this one (`115_doc_draft_discard` is the last). --- .../115_doc_draft_discard/00_phase.md | 0 .../01_delete_draft_route.md | 0 .../115_doc_draft_discard/02_discard_ui.md | 0 .../03_draft_title_fix.md | 0 .../04_draft_discard_tests.md | 0 .../115_doc_draft_discard__00_phase.a1.err | 0 .../115_doc_draft_discard__00_phase.a1.md | 13 + ...15_doc_draft_discard__00_phase.a1.validate | 103 ++++++ ...raft_discard__01_delete_draft_route.a1.err | 0 ...draft_discard__01_delete_draft_route.a1.md | 15 + ...discard__01_delete_draft_route.a1.validate | 102 ++++++ ...15_doc_draft_discard__02_discard_ui.a1.err | 0 ...115_doc_draft_discard__02_discard_ui.a1.md | 10 + ...c_draft_discard__02_discard_ui.a1.validate | 102 ++++++ ...c_draft_discard__03_draft_title_fix.a1.err | 0 ...oc_draft_discard__03_draft_title_fix.a1.md | 17 + ...ft_discard__03_draft_title_fix.a1.validate | 103 ++++++ ...aft_discard__04_draft_discard_tests.a1.err | 0 ...raft_discard__04_draft_discard_tests.a1.md | 10 + ...iscard__04_draft_discard_tests.a1.validate | 103 ++++++ app/api/doc_drafts.py | 23 +- frontend/assets/app.js | 62 +++- frontend/assets/doc-edit.js | 69 ++++ frontend/assets/styles.css | 34 +- frontend/doc-edit.html | 14 + tests/e2e/test_save_doc_session.py | 189 +++++++++- tests/integration/test_doc_drafts_api.py | 58 +++ tests/unit/test_frontend_doc_draft_discard.py | 329 ++++++++++++++++++ tests/unit/test_save_as_doc_button.py | 58 ++- 29 files changed, 1384 insertions(+), 30 deletions(-) rename .agents/phases/{todo => complete}/115_doc_draft_discard/00_phase.md (100%) rename .agents/phases/{todo => complete}/115_doc_draft_discard/01_delete_draft_route.md (100%) rename .agents/phases/{todo => complete}/115_doc_draft_discard/02_discard_ui.md (100%) rename .agents/phases/{todo => complete}/115_doc_draft_discard/03_draft_title_fix.md (100%) rename .agents/phases/{todo => complete}/115_doc_draft_discard/04_draft_discard_tests.md (100%) create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.err create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.md create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.validate create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.err create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.md create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.validate create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.err create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.md create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.validate create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.err create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.md create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.validate create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.err create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.md create mode 100644 .agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.validate create mode 100644 tests/unit/test_frontend_doc_draft_discard.py diff --git a/.agents/phases/todo/115_doc_draft_discard/00_phase.md b/.agents/phases/complete/115_doc_draft_discard/00_phase.md similarity index 100% rename from .agents/phases/todo/115_doc_draft_discard/00_phase.md rename to .agents/phases/complete/115_doc_draft_discard/00_phase.md diff --git a/.agents/phases/todo/115_doc_draft_discard/01_delete_draft_route.md b/.agents/phases/complete/115_doc_draft_discard/01_delete_draft_route.md similarity index 100% rename from .agents/phases/todo/115_doc_draft_discard/01_delete_draft_route.md rename to .agents/phases/complete/115_doc_draft_discard/01_delete_draft_route.md diff --git a/.agents/phases/todo/115_doc_draft_discard/02_discard_ui.md b/.agents/phases/complete/115_doc_draft_discard/02_discard_ui.md similarity index 100% rename from .agents/phases/todo/115_doc_draft_discard/02_discard_ui.md rename to .agents/phases/complete/115_doc_draft_discard/02_discard_ui.md diff --git a/.agents/phases/todo/115_doc_draft_discard/03_draft_title_fix.md b/.agents/phases/complete/115_doc_draft_discard/03_draft_title_fix.md similarity index 100% rename from .agents/phases/todo/115_doc_draft_discard/03_draft_title_fix.md rename to .agents/phases/complete/115_doc_draft_discard/03_draft_title_fix.md diff --git a/.agents/phases/todo/115_doc_draft_discard/04_draft_discard_tests.md b/.agents/phases/complete/115_doc_draft_discard/04_draft_discard_tests.md similarity index 100% rename from .agents/phases/todo/115_doc_draft_discard/04_draft_discard_tests.md rename to .agents/phases/complete/115_doc_draft_discard/04_draft_discard_tests.md diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.err b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.md b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.md new file mode 100644 index 0000000..23c86a1 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.md @@ -0,0 +1,13 @@ +All green — this was the final verification pass; everything from the four completed tasks was already in the working tree and verified. + +**Phase 115 — Doc drafts: Discard + DELETE route + title fix — verification report** + +- Verified all 4 task deliverables present: DELETE route (`app/api/doc_drafts.py`), Discard UI (`doc-edit.html` + `doc-edit.js` + `.discard-draft` CSS), title fix (`defaultDocTitle(wrap)` pairing + `saveAsDoc` call site), and all test pins (integration, frontend unit, E2E). No code changes needed. +- **Completion criteria:** + 1. ✅ Orphaned draft discardable from edit screen; row gone — `test_delete_removes_row_and_invalidates_token` (204 → GET 404), unknown-token 404, admin-gate 403 on all routes, E2E `test_discard_draft_from_edit_screen` all pass. + 2. ✅ Title after retry redo = redone answer's own question — E2E `test_save_title_is_the_redo_question_after_retry` passes. + 3. ✅ Push flow byte-identical — `git diff` shows only the new DELETE route + module docstring; all 7 existing push tests green. + 4. ✅ `uv run pytest --cov=app` → **2457 passed**, app coverage **99%** (>90%); `uv run pytest tests/e2e/test_save_doc_session.py -v --no-cov` → **4 passed**; `uv run ruff check .` → clean; `uv run pyright` → 0 errors. + 5. ⏳ Commit + phase-dir move left to the harness (per executor rules, no `git` run; all changes left in the working tree). +- No defects found; no deviations. +- Next pending phase: none in `todo/` other than this one (`115_doc_draft_discard` is the last). diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.validate b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.validate new file mode 100644 index 0000000..6b977c2 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__00_phase.a1.validate @@ -0,0 +1,103 @@ +........................................................................ [ 2%] +........................................................................ [ 5%] +........................................................................ [ 8%] +........................................................................ [ 11%] +........................................................................ [ 14%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 26%] +........................................................................ [ 29%] +........................................................................ [ 32%] +........................................................................ [ 35%] +........................................................................ [ 38%] +........................................................................ [ 41%] +........................................................................ [ 43%] +........................................................................ [ 46%] +........................................................................ [ 49%] +........................................................................ [ 52%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 64%] +........................................................................ [ 67%] +........................................................................ [ 70%] +........................................................................ [ 73%] +........................................................................ [ 76%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 84%] +........................................................................ [ 87%] +........................................................................ [ 90%] +........................................................................ [ 93%] +........................................................................ [ 96%] +........................................................................ [ 99%] +......... [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/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.14.7-final-0 ________________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 224 1 99% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 99 0 100% +app/api/docs.py 156 1 99% +app/api/git_sources.py 232 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 139 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 210 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/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 22 0 100% +app/main.py 66 0 100% +app/models.py 128 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 317 1 99% +app/rag/archive_upload.py 134 0 100% +app/rag/chunker.py 206 4 98% +app/rag/doc_dates.py 18 0 100% +app/rag/folder_summaries.py 123 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 215 3 99% +app/rag/llm.py 244 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 192 3 98% +app/rag/scaffolding.py 55 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 24 0 100% +app/schemas.py 329 0 100% +-------------------------------------------------- +TOTAL 4147 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 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.err b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.md b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.md new file mode 100644 index 0000000..d26c3ef --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.md @@ -0,0 +1,15 @@ +Task 01 complete. Report: + +**Implemented** +- `DELETE /api/doc-drafts/{token}` in `app/api/doc_drafts.py` — 204 No Content, deletes the row via the existing `_get_draft_or_404` helper (unknown token → 404, malformed → 422); admin-gated by the router-level `require_admin` (locked A2). Placed after PUT, before push; file's int-literal status-code idiom used (no `status` import exists). Module docstring route list updated. +- Integration tests in `tests/integration/test_doc_drafts_api.py`: delete → 204 + row gone from Postgres + GET/PUT/push all 404; unknown token → 404; malformed token → 422; works on a `pushed` draft (no push-side state guards it); anonymous DELETE → 403 added to the existing "403 on all routes" test. + +**Results** +- `uv run pytest tests/integration/test_doc_drafts_api.py -v --no-cov` → 32 passed +- `uv run pytest --cov=app --cov-report=term-missing` → 2448 passed, TOTAL 99% (>90%), `app/api/doc_drafts.py` 100% +- `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings + +**Notes** +- No migration, no other route touched; push flow byte-identical. Core route tests added now (completion criteria) — task 04 will extend with frontend/E2E pins. + +**Next pending task:** `02_discard_ui.md` (Discard control on the doc-edit screen). diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.validate b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.validate new file mode 100644 index 0000000..32044d3 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__01_delete_draft_route.a1.validate @@ -0,0 +1,102 @@ +........................................................................ [ 2%] +........................................................................ [ 5%] +........................................................................ [ 8%] +........................................................................ [ 11%] +........................................................................ [ 14%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 26%] +........................................................................ [ 29%] +........................................................................ [ 32%] +........................................................................ [ 35%] +........................................................................ [ 38%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 47%] +........................................................................ [ 50%] +........................................................................ [ 52%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 64%] +........................................................................ [ 67%] +........................................................................ [ 70%] +........................................................................ [ 73%] +........................................................................ [ 76%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 85%] +........................................................................ [ 88%] +........................................................................ [ 91%] +........................................................................ [ 94%] +........................................................................ [ 97%] +........................................................................ [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/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.14.7-final-0 ________________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 224 1 99% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 99 0 100% +app/api/docs.py 156 1 99% +app/api/git_sources.py 232 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 139 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 210 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/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 22 0 100% +app/main.py 66 0 100% +app/models.py 128 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 317 1 99% +app/rag/archive_upload.py 134 0 100% +app/rag/chunker.py 206 4 98% +app/rag/doc_dates.py 18 0 100% +app/rag/folder_summaries.py 123 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 215 3 99% +app/rag/llm.py 244 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 192 3 98% +app/rag/scaffolding.py 55 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 24 0 100% +app/schemas.py 329 0 100% +-------------------------------------------------- +TOTAL 4147 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 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.err b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.md b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.md new file mode 100644 index 0000000..69dd564 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.md @@ -0,0 +1,10 @@ +All green. Final report: + +**Task 02 — Discard control on the doc-edit screen — complete** + +- `frontend/doc-edit.html`: "Discard draft" button (`#discard-draft`, exact task-spec markup) in `.doc-edit-actions` between the push button and the back link, with a phase-115 comment +- `frontend/assets/doc-edit.js`: `wireDiscard()` — native `confirm()` first → `DELETE /api/doc-drafts/${draftToken}` (same token as GET/PUT) → 204 → `location.assign("/")`; non-204 → stale status cleared + `#push-error` inline banner (422-shape-aware `apiDetail`), no navigation; network failure → fixed one-line copy; §7.4 in-flight state (disable + "Discarding…", restored in `finally`); no-token guard mirrors push; file-header contract doc updated +- `frontend/assets/styles.css`: `.discard-draft` — secondary ghost family (`.steering-delete`/`.tuning-delete` convention: ink-soft/`--line`, err-family hover 9.3:1, 44px touch floor, `:disabled` state); global 3px `:focus-visible` applies +- Decision: task-spec native `confirm()` used — phase 69's alertdialog is page-local to the SPA Sources view, not a shared/theme asset, so the task's escape hatch doesn't apply +- Tests: `uv run pytest` → 2448 passed; `--cov=app` → 99% (>90% gate); `ruff check .` clean; `pyright` 0 errors; `node --check` valid; TestClient smoke: served `/doc-edit.html` 200 with the button. (Task's own unit/E2E pins ship in task 04 per its Testing section — not started) +- Next pending task: `.agents/phases/todo/115_doc_draft_discard/03_draft_title_fix.md` diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.validate b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.validate new file mode 100644 index 0000000..32044d3 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__02_discard_ui.a1.validate @@ -0,0 +1,102 @@ +........................................................................ [ 2%] +........................................................................ [ 5%] +........................................................................ [ 8%] +........................................................................ [ 11%] +........................................................................ [ 14%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 26%] +........................................................................ [ 29%] +........................................................................ [ 32%] +........................................................................ [ 35%] +........................................................................ [ 38%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 47%] +........................................................................ [ 50%] +........................................................................ [ 52%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 64%] +........................................................................ [ 67%] +........................................................................ [ 70%] +........................................................................ [ 73%] +........................................................................ [ 76%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 85%] +........................................................................ [ 88%] +........................................................................ [ 91%] +........................................................................ [ 94%] +........................................................................ [ 97%] +........................................................................ [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/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.14.7-final-0 ________________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 224 1 99% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 99 0 100% +app/api/docs.py 156 1 99% +app/api/git_sources.py 232 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 139 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 210 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/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 22 0 100% +app/main.py 66 0 100% +app/models.py 128 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 317 1 99% +app/rag/archive_upload.py 134 0 100% +app/rag/chunker.py 206 4 98% +app/rag/doc_dates.py 18 0 100% +app/rag/folder_summaries.py 123 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 215 3 99% +app/rag/llm.py 244 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 192 3 98% +app/rag/scaffolding.py 55 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 24 0 100% +app/schemas.py 329 0 100% +-------------------------------------------------- +TOTAL 4147 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 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.err b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.md b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.md new file mode 100644 index 0000000..29b0acb --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.md @@ -0,0 +1,17 @@ +All green. Task 03 complete. + +**Implemented** +- `frontend/assets/app.js`: `defaultDocTitle(wrap)` — when the saved brain bubble's wrap is given, the title is the nearest preceding `.msg.user` bubble's `.bubble` text (DOM-structural pairing, redo-in-place safe); falls back to the last-user-record-in-`conversation` rule when wrap is absent or no pair found. `DOC_TITLE_MAX` slice / whitespace collapse / "Note" unchanged. +- `saveAsDoc(btn)` call site now passes `btn.closest(".msg.brain")`; house doc comments updated. +- Unit pins (`tests/unit/test_save_as_doc_button.py`): replaced the stale last-question test with `test_app_js_default_title_is_the_paired_user_question` + `test_app_js_save_as_doc_passes_the_bubble_ancestor` (incl. "no caller stays on the no-wrap fallback" pin). +- Updated the deliberate behavior change in `tests/e2e/test_save_doc_session.py::test_earlier_bubble_button_saves_whole_session` — saving from the FIRST bubble now titles Q1 (its own question) instead of Q3; last-bubble flow (Q3) is regression-checked unchanged. + +**Results** +- `uv run pytest` → exit 0, all pass +- `uv run pytest --cov=app --cov-report=term-missing` → exit 0, `app/` TOTAL 99% (>90% gate) +- `uv run pytest tests/e2e/test_save_doc_session.py -v --no-cov` → 2 passed in isolation +- `uv run ruff check . && uv run pyright` → clean (0 errors) + +**Decisions:** title text read from the user bubble's `.bubble` (not whole wrap — meta rows carry button labels). No git commit made (harness commits per phase). + +**Next pending task:** `115_doc_draft_discard/04_draft_discard_tests.md` (API + frontend + E2E pins, incl. the title-after-retry pin). diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.validate b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.validate new file mode 100644 index 0000000..00e2de8 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__03_draft_title_fix.a1.validate @@ -0,0 +1,103 @@ +........................................................................ [ 2%] +........................................................................ [ 5%] +........................................................................ [ 8%] +........................................................................ [ 11%] +........................................................................ [ 14%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 26%] +........................................................................ [ 29%] +........................................................................ [ 32%] +........................................................................ [ 35%] +........................................................................ [ 38%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 47%] +........................................................................ [ 49%] +........................................................................ [ 52%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 64%] +........................................................................ [ 67%] +........................................................................ [ 70%] +........................................................................ [ 73%] +........................................................................ [ 76%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 85%] +........................................................................ [ 88%] +........................................................................ [ 91%] +........................................................................ [ 94%] +........................................................................ [ 97%] +........................................................................ [ 99%] +. [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/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.14.7-final-0 ________________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 224 1 99% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 99 0 100% +app/api/docs.py 156 1 99% +app/api/git_sources.py 232 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 139 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 210 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/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 22 0 100% +app/main.py 66 0 100% +app/models.py 128 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 317 1 99% +app/rag/archive_upload.py 134 0 100% +app/rag/chunker.py 206 4 98% +app/rag/doc_dates.py 18 0 100% +app/rag/folder_summaries.py 123 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 215 3 99% +app/rag/llm.py 244 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 192 3 98% +app/rag/scaffolding.py 55 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 24 0 100% +app/schemas.py 329 0 100% +-------------------------------------------------- +TOTAL 4147 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 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.err b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.md b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.md new file mode 100644 index 0000000..169ef88 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.md @@ -0,0 +1,10 @@ +All green. Final report: + +**Phase 115 task 04 — API + frontend + E2E pins for discard + title fix** + +- **Integration** (`tests/integration/test_doc_drafts_api.py`): DELETE section already landed in-tree by tasks 01–03 (204 + row gone + GET/PUT/push 404, unknown-token 404, malformed 422, anonymous 403, pushed-draft discard) — verified all 32 pass. +- **Unit (new)** `tests/unit/test_frontend_doc_draft_discard.py`: 8 house source-assertion pins — `#discard-draft` placement/copy in the actions row; confirm-before-DELETE ordering; same `draftToken` credential; 204→`location.assign("/")` as the file's only navigation (code-only count), non-204→inline error no navigation; never-stale "Discarding…" lifecycle; `defaultDocTitle(wrap)` paired-bubble preference + last-record fallback; `saveAsDoc` passes `btn.closest(".msg.brain")`; `.discard-draft` ghost family (44px, err hover, disabled, focus-visible). +- **E2E (extended)** `tests/e2e/test_save_doc_session.py`: (3) discard flow — save → edit screen → dismiss confirm (screen + draft intact, API 200) → accept → back on chat; draft gone (admin API GET 404 + Postgres row count 0); (4) title after Retry redo-in-place — redo (tagged old wrap leaves DOM, no question dup) → trailing Q3 → save redone answer → title/path == redone Q2, body byte-exact whole-session transcript. Dialog policy via one registered `page.on("dialog")` handler (listener-less dialogs are auto-dismissed by Playwright — fixed after 2 failed attempts). +- **Results**: `uv run pytest --cov=app --cov-report=term-missing` → exit 0, `TOTAL 4147 16 99%` (>90% ✓); `uv run pytest tests/e2e/test_save_doc_session.py -v --no-cov` → 4 passed; `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors. +- **Decisions**: no app-code changes needed (route/UI/title fixes shipped by tasks 01–03, all verified working); E2E row-gone check adds a direct Postgres count on top of the API 404 (the 404 alone would pass for any unknown token). +- **Next pending task**: none in `115_doc_draft_discard` (04 was the last) — the pipeline gate moves the phase to `complete/`; next phase in `todo/` per roadmap ordering. diff --git a/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.validate b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.validate new file mode 100644 index 0000000..6b977c2 --- /dev/null +++ b/.agents/reports/115_doc_draft_discard/115_doc_draft_discard__04_draft_discard_tests.a1.validate @@ -0,0 +1,103 @@ +........................................................................ [ 2%] +........................................................................ [ 5%] +........................................................................ [ 8%] +........................................................................ [ 11%] +........................................................................ [ 14%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 26%] +........................................................................ [ 29%] +........................................................................ [ 32%] +........................................................................ [ 35%] +........................................................................ [ 38%] +........................................................................ [ 41%] +........................................................................ [ 43%] +........................................................................ [ 46%] +........................................................................ [ 49%] +........................................................................ [ 52%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 64%] +........................................................................ [ 67%] +........................................................................ [ 70%] +........................................................................ [ 73%] +........................................................................ [ 76%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 84%] +........................................................................ [ 87%] +........................................................................ [ 90%] +........................................................................ [ 93%] +........................................................................ [ 96%] +........................................................................ [ 99%] +......... [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/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.14.7-final-0 ________________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 224 1 99% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 99 0 100% +app/api/docs.py 156 1 99% +app/api/git_sources.py 232 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 139 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 210 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/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 22 0 100% +app/main.py 66 0 100% +app/models.py 128 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 317 1 99% +app/rag/archive_upload.py 134 0 100% +app/rag/chunker.py 206 4 98% +app/rag/doc_dates.py 18 0 100% +app/rag/folder_summaries.py 123 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 215 3 99% +app/rag/llm.py 244 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 192 3 98% +app/rag/scaffolding.py 55 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 24 0 100% +app/schemas.py 329 0 100% +-------------------------------------------------- +TOTAL 4147 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 diff --git a/app/api/doc_drafts.py b/app/api/doc_drafts.py index cb49f95..e61df84 100644 --- a/app/api/doc_drafts.py +++ b/app/api/doc_drafts.py @@ -19,7 +19,10 @@ token — 404 ``draft not found`` when unknown), ``PUT /api/doc-drafts/{token}`` (partial update — absent fields unchanged, ``updated_at`` bumped; editing a ``pushed`` draft resets ``status`` back to ``draft`` — the stored sha no longer describes the current -body, so the next push re-commits; phase 59 D3 ASSUMPTION), ``POST +body, so the next push re-commits; phase 59 D3 ASSUMPTION), ``DELETE +/api/doc-drafts/{token}`` (discard — 204 No Content; the row is +removed, so every subsequent GET/PUT/push 404s; phase 115, the edit +screen's Discard control), ``POST /api/doc-drafts/{token}/push`` (the single mutation the edit screen triggers — commit + ``git push`` the draft's file to the ``BOR_DOCS_REPO`` ``BOR_DOCS_BRANCH`` via @@ -233,6 +236,24 @@ def update_draft( return _to_out(row) +@router.delete("/{token}", status_code=204) +def delete_draft( + token: uuid.UUID, + db: Session = Depends(get_db), # noqa: B008 +) -> None: + """Discard a draft (the edit screen's Discard control, phase 115). + + Admin-gated like the whole router (phase 59); the uuid4 token is + the screen's credential — after a successful discard, GET/PUT/push + all 404. 204 No Content (no body): the token is a one-way + credential and nothing else references the row (no FK targets, no + push-side state — the git push happens only on push). + """ + row = _get_draft_or_404(db, token) + db.delete(row) + db.commit() + + @router.post("/{token}/push", response_model=DocDraftPushed) def push_doc_draft( token: uuid.UUID, diff --git a/frontend/assets/app.js b/frontend/assets/app.js index 1809084..e32988b 100644 --- a/frontend/assets/app.js +++ b/frontend/assets/app.js @@ -596,9 +596,11 @@ function markLastRetryable() { * keeps it rightmost when the last bubble also carries the Retry * button. * - * Click: default title (UNCHANGED by phase 75 — the LAST user - * question, whitespace-collapsed, ≤120 chars, the phase-50 auto-title - * convention) + default in-repo path (docs/.md) + the + * Click: default title (phase 115 — the QUESTION THE ANSWER + * ANSWERED: the saved bubble's paired user bubble, whitespace- + * collapsed, ≤120 chars, the phase-50 auto-title convention; the + * last-conversation-record rule survives as the no-pair fallback) + + * default in-repo path (docs/.md) + the * FULL-SESSION transcript as the body (phase 75 A6) → POST * /api/doc-drafts {title, path, body} → 201 → * /doc-edit.html?draft= (the edit screen, task 06, owns the @@ -609,19 +611,47 @@ const SAVE_AS_DOC_ICON = const DOC_TITLE_MAX = 120; // the phase-50 auto-title cap (owner-locked) -/* The default doc title: the LAST user question's text, - * whitespace-collapsed, truncated to 120 chars — the phase-50 - * auto-title convention (server-side: " ".join(text.split())[:120]) - * applied to the last question. Defensive "Note" when the - * conversation has no user record (the UI cannot produce one). +/* The default doc title. Phase 115 (task 03 — the TODO L7 side + * observation): when the saved brain bubble's *wrap* is given, the + * title comes from the user bubble PAIRED with it — the nearest + * preceding .msg.user in the DOM conversation flow (#messages' direct + * children are the bubbles, in order; the walk is over the wrap's + * previousElementSibling chain). The pairing is DOM-structural, not + * index-based: redo-in-place (phase 49) reorders the DOM, and the + * structural pair IS the answer's question by construction — the + * last conversation record, after a retry, can be an unrelated + * trailing question (the junk-title edge case). The text is read + * from the bubble's .bubble (the rendered question — the meta rows + * with their button labels live on brain bubbles, never user ones). + * No wrap given, or no paired user bubble found (first-turn edge / + * DOM mismatch) → the pre-phase-115 fallback: the LAST user record + * in `conversation`. Whitespace-collapsed, truncated to 120 chars — + * the phase-50 auto-title convention (server-side: + * " ".join(text.split())[:120]); defensive "Note" when neither + * source yields a question (the UI cannot produce one). * " ".join(split()) == replace(/\s+/g, " ").trim() for non-empty * input; the trim keeps the leading/trailing-whitespace edge identical. */ -function defaultDocTitle() { +function defaultDocTitle(wrap) { let question = ""; - for (let i = conversation.length - 1; i >= 0; i -= 1) { - if (conversation[i].who === "user") { - question = conversation[i].text; - break; + if (wrap) { + for ( + let el = wrap.previousElementSibling; + el !== null; + el = el.previousElementSibling + ) { + if (el.classList.contains("msg") && el.classList.contains("user")) { + const bubble = el.querySelector(".bubble"); + question = bubble ? (bubble.textContent ?? "") : ""; + break; + } + } + } + if (!question) { + for (let i = conversation.length - 1; i >= 0; i -= 1) { + if (conversation[i].who === "user") { + question = conversation[i].text; + break; + } } } return question.replace(/\s+/g, " ").trim().slice(0, DOC_TITLE_MAX) || "Note"; @@ -721,7 +751,11 @@ async function saveAsDoc(btn) { if (btn.disabled) return; // one save at a time (double-click guard) btn.disabled = true; try { - const title = defaultDocTitle(); + // Phase 115 (task 03): pass the button's OWN bubble (the .save- + // as-doc-btn lives in the bubble's .msg-meta row, so closest + // climbs button → meta → body → the .msg.brain wrap) — the title + // pairs the answer with ITS question (the redo-in-place fix). + const title = defaultDocTitle(btn.closest(".msg.brain")); const path = `docs/${docSlug(title)}.md`; const res = await fetch("/api/doc-drafts", { method: "POST", diff --git a/frontend/assets/doc-edit.js b/frontend/assets/doc-edit.js index 5464f58..b9259be 100644 --- a/frontend/assets/doc-edit.js +++ b/frontend/assets/doc-edit.js @@ -45,6 +45,24 @@ * an edit, not a re-type) and the button re-enables; * • network failure → the fixed one-line copy, same recovery. * + * Discard (phase 115, task 02 — the page's one destructive action, + * on #discard-draft): a native ``confirm()`` FIRST (destructive + + * irreversible — no undo exists; the shell's alertdialog pattern is + * page-local to the SPA's Sources view, not a shared asset), then + * ``DELETE /api/doc-drafts/`` (the same uuid4 token the GET/ + * PUT ran on — the screen's credential; the router is admin-gated + * regardless): + * • 204 → ``location.assign("/")`` — back to the chat (the draft + * has no other home: no drafts list exists); + * • non-204 (a 404 race — the row vanished under us) → the + * #push-error inline banner with the server's detail (422 + * shape-aware), the stale success line cleared (one claim at a + * time), NO navigation, no crash; + * • network failure → the fixed one-line copy, same recovery. + * The button runs the §7.4 in-flight lifecycle (disable + + * "Discarding…" while the DELETE is out; restored in the finally — + * never stale). + * * The shared header module loads through this script's own relative * import ("./header.js") — a hoisted import evaluated before this body * runs (single-evaluation design: no direct