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).
5.7 KiB
Phase 115 — Doc drafts: Discard + DELETE route + title fix (TODO L7)
Source: TODO.md L183–209 — "L7 — 'Save as doc' has no Discard: orphan drafts are invisible and un-deletable (2026-09-15, brain-of-reese interactive test)"
Story: n/a (interactive-test follow-up fix; extends the phase-59/75 doc-draft and save-as-doc assets).
Context: "Save as doc" (frontend/assets/app.js::saveAsDoc, L720) POSTs /api/doc-drafts (201) → /doc-edit.html?draft=<token>. The edit screen offers exactly one action: "Push to docs branch". The drafts router (app/api/doc_drafts.py) has POST/GET/PUT/POST-push only — no DELETE, no TTL/pruning; a draft created by an accidental click (or a tester) sits orphaned in the DB forever — invisible (no UI lists drafts) and only consumable by actually pushing a doc to the repo. Side observation: the draft's default title comes from defaultDocTitle() (app.js:619) — the last user record in the conversation — and after a Retry redo-in-place (phase 49) the redone answer sits at the end, so its title came from an unrelated trailing question (a junk 4,000-char test question, not the question the answer answered). The body (full session transcript) is correct by design; only the title derivation mismatches.
Objective
An orphaned doc draft can be discarded from the edit screen (a new admin-gated DELETE /api/doc-drafts/{token} + a Discard control), and the draft's default title is the question the saved answer actually answered (its paired user record) — fixing the retry-redo mismatch.
Dependencies
114_embed_question_length(todo) — pipeline predecessor (execution order) only; no code dependency.
Design (shared by all tasks — the executor reads this, not the chat)
- DELETE route (task 01):
DELETE /api/doc-drafts/{token}→ 204. The whole router already sits behindrequire_admin(phase 59,dependencies=[Depends(require_admin)]at L63) — the new route inherits it; the uuid4 token is the screen's credential (same trust model as GET/PUT/push). Unknown token → 404 via the existing_get_draft_or_404helper. No migration (a row delete); no push-side state (the git push happens only on push). - Discard UI (task 02): the doc-edit screen — a "Discard draft" control next to "Push to docs branch" (secondary/danger treatment per the theme).
frontend/assets/doc-edit.js:confirm()(destructive + irreversible — no undo exists),DELETE /api/doc-drafts/${token}(the same token the screen already uses for GET/PUT) → 204 →location.assign("/")(back to the chat page). Non-204 → the page's existing inline-error pattern, no navigation. - Title fix (task 03):
defaultDocTitle(wrap)— takes the saved brain bubble's wrap (the.save-as-doc-btn's bubble); the title is the text of the user bubble paired with that brain bubble (the nearest preceding user message in the DOM conversation flow), falling back to the current last-user-record-in-conversationlogic when no wrap is given or no paired user bubble is found (first-turn edge / DOM mismatch). TheDOC_TITLE_MAXslice + "Note" fallback are unchanged. - NOT touched: the push flow (byte-identical), the draft body (
buildSessionTranscript— full session, correct by design), the drafts schema (no field change), no TTL/pruning (locked A1 — out of scope).
Tasks
01_delete_draft_route.md—DELETE /api/doc-drafts/{token}(204 / 404 / admin-gated).02_discard_ui.md— the Discard control on the doc-edit screen wired to the route.03_draft_title_fix.md— the title from the answer's own question (the paired user record).04_draft_discard_tests.md— the API + frontend + E2E pins.
Testing & Quality
- Integration:
tests/integration/test_doc_drafts_api.py(existing — extend, task 04): DELETE removes the row (204; subsequent GET 404); unknown token → 404; the admin gate applies (same assertions the sibling routes use). - Unit (frontend, house source-assertion style, task 04): the Discard control's presence/handler (confirm → DELETE → 204 → redirect; non-204 → inline error, no navigation); the
defaultDocTitlepairing logic + the call site. - E2E:
tests/e2e/test_save_doc_session.py(existing — extend, task 04): the discard flow (save → edit screen → discard → confirm → back on the chat, draft gone); a save-as-doc after a Retry redo-in-place → the title matches the redone answer's own question. Run in isolation:uv run pytest tests/e2e/test_save_doc_session.py -v --no-cov. - Coverage: >90% on
app/(validate.sh gate).
Completion Criteria
- An orphaned draft can be discarded from the edit screen; the draft row is gone afterward (integration).
- The title of a save-as-doc after a retry redo matches the redone answer's own question (E2E).
- The push flow is byte-identical (regression green).
uv run pytestgreen; coverage >90%; the e2e file green in isolation;uv run ruff check . && uv run pyrightclean.- One
--no-gpg-signcommit; phase dir moved tocomplete/by the pipeline gate.
Locked decisions
- A1 — scope: the Discard control + the DELETE route + the title fix; NO TTL/pruning (owner-confirmed 2026-09-14, roadmap confirmation — the discard covers the acceptance; pruning can be a future phase).
- A2 — the DELETE route is admin-gated by the router-level
require_admin(phase 59) with the uuid4 token as the credential — same trust model as the sibling routes (owner-confirmed 2026-09-14).
Commit
git add app/ tests/ frontend/ .agents/phases/ && git commit --no-gpg-sign -m "feat(docs): discard doc drafts from the edit screen (DELETE /api/doc-drafts/{token}) + derive the draft title from the answer's own question"