From e1b8ba012d3cc0a7271adae747127488997a7fd7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 12 Sep 2026 22:37:46 +0200 Subject: [PATCH] fix: the AI draft opens what it wrote, and Editorial opens the editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **"Draft with AI does nothing" was almost true.** The job worked — about forty seconds, then a draft saved — but the panel closed onto a library that looked unchanged, and the only progress it ever showed was the same "Drafting…" line. The job now reports the article it made and the page goes straight into it, and the wait is counted in seconds so it is visibly a wait rather than a hang. **Editorial rows open the editor.** It is the queue of things to *do* to an article; rows that opened the reader made an editor press Edit on every one. **No Contents button on a phone.** The header's menu opens the contents, and a second door in the prose is the same mistake the player had. An open drawer now has a backdrop that closes it, and choosing a section closes it too — it sits over the prose it just scrolled to. **References wrap.** A source title is often a filename with underscores and no spaces to break at, so the list ran off the side of the screen. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN --- backend/app/routers/articles.py | 8 ++++++++ backend/app/tasks/quiz_tasks.py | 2 ++ frontend/src/components/ArticleReader.jsx | 9 ++++++++- frontend/src/pages/ArticlesPage.css | 18 ++++++++++++++++-- frontend/src/pages/ArticlesPage.jsx | 20 ++++++++++++++++++-- frontend/src/pages/EditorialPage.jsx | 6 +++++- 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/backend/app/routers/articles.py b/backend/app/routers/articles.py index aaa1375..ac776d2 100644 --- a/backend/app/routers/articles.py +++ b/backend/app/routers/articles.py @@ -981,6 +981,14 @@ def get_article_job(job_id: str, current_user: User = Depends(get_current_user)) status = r.get(f"extraction:status:{job_id}") or "unknown" steps = [_json.loads(s) for s in r.lrange(f"extraction:steps:{job_id}", 0, -1)] result = {"job_id": job_id, "status": status, "steps": steps} + # What it made, so the page that started the job can open it. Without this + # a draft finished into a list somewhere and the educator who asked for it + # was left looking at a panel that had simply closed. + # Parsed defensively: an older job has no such key, and anything that is + # not a number means the same thing as nothing — no article to open. + article_id = r.get(f"extraction:article:{job_id}") + if article_id and str(article_id).isdigit(): + result["article_id"] = int(article_id) if status == "failed": result["error"] = r.get(f"extraction:error:{job_id}") or "Unknown error" return result diff --git a/backend/app/tasks/quiz_tasks.py b/backend/app/tasks/quiz_tasks.py index 9023190..7f9d564 100644 --- a/backend/app/tasks/quiz_tasks.py +++ b/backend/app/tasks/quiz_tasks.py @@ -1068,6 +1068,8 @@ def generate_article_draft(self, job_id: str, user_id: int, topic: str, # `Article.sections` has to do this; the ones that did not left 323 # articles with no section rows and a vector built from the title alone. article_service.reindex(db, article) + # The id, so whoever asked for the draft can be taken straight to it. + r.set(f"extraction:article:{job_id}", str(article.id), ex=EXPIRE_SECONDS) r.set(f"extraction:status:{job_id}", "completed", ex=EXPIRE_SECONDS) _push_step(r, job_id, "done", f"Draft saved: {title}") except ArticleDraftError as refusal: diff --git a/frontend/src/components/ArticleReader.jsx b/frontend/src/components/ArticleReader.jsx index eaa5105..919b837 100644 --- a/frontend/src/components/ArticleReader.jsx +++ b/frontend/src/components/ArticleReader.jsx @@ -371,6 +371,13 @@ export default function ArticleReader({ return (
+ {/* Tapping anywhere outside an open drawer closes it. A real element + rather than a CSS pseudo-element, because a backdrop has to be + clickable and `::after` cannot take a listener. */} + {drawerOpen && ( +