phase: 115_doc_draft_discard
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).
This commit is contained in:
@@ -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/<token>`` (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 <script> tag; esbuild
|
||||
@@ -252,6 +270,57 @@ function wirePush() {
|
||||
|
||||
wirePush();
|
||||
|
||||
/* ---------- discard (DELETE /api/doc-drafts/<token> — phase 115) ----------
|
||||
* The page's one destructive action: confirm → DELETE the row (the
|
||||
* same uuid4 token the GET/PUT ran on) → 204 → back to the chat (the
|
||||
* draft has no other home — no drafts list exists). Every failure
|
||||
* lands the inline error banner (the server's detail — 422
|
||||
* shape-aware) and does NOT navigate: never stale, no crash. */
|
||||
const DISCARD_LABEL = "Discard draft";
|
||||
|
||||
function wireDiscard() {
|
||||
const discardBtn = document.querySelector("#discard-draft");
|
||||
if (!discardBtn) return;
|
||||
discardBtn.addEventListener("click", async () => {
|
||||
if (!draftToken) {
|
||||
showError("No draft specified.");
|
||||
return;
|
||||
}
|
||||
// Destructive + irreversible — no undo exists. The native
|
||||
// confirm() is the approved dialog for this one action on the slim
|
||||
// flow page (phase 115 task 02 ASSUMPTION — the shell's
|
||||
// alertdialog is page-local to the Sources view, not a shared
|
||||
// asset).
|
||||
if (!confirm("Discard this draft? This cannot be undone.")) return;
|
||||
discardBtn.disabled = true; // §7.4: one discard per click
|
||||
discardBtn.textContent = "Discarding…";
|
||||
try {
|
||||
const r = await fetch(`/api/doc-drafts/${draftToken}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (r.status === 204) {
|
||||
// The row is gone — the chat page is the draft's only other
|
||||
// home.
|
||||
location.assign("/");
|
||||
return;
|
||||
}
|
||||
// Non-204 (a 404 race — the row is gone — or any other failure):
|
||||
// the server line in the inline banner, the stale success line
|
||||
// cleared (one claim at a time), NO navigation, no crash.
|
||||
setStatus("");
|
||||
showError(await apiDetail(r, `Could not discard the draft (${r.status}).`));
|
||||
} catch {
|
||||
setStatus("");
|
||||
showError("Could not reach the server — is the app running?");
|
||||
} finally {
|
||||
discardBtn.disabled = false; // never stale — success OR failure
|
||||
discardBtn.textContent = DISCARD_LABEL;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
wireDiscard();
|
||||
|
||||
/* ---------- boot ----------
|
||||
* The admin gate FIRST (the sources-gate pattern — one cached
|
||||
* whoami): anonymous visitors get the gate and NO /api/doc-drafts
|
||||
|
||||
Reference in New Issue
Block a user