[Frontend] Make the wiki proposal/draft review pages robust: busy states, inline errors, and a poll timeout #395
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Impact: MEDIUM
Found in the August 2026 session lifecycle review (#319).
What the user experiences
Several wiki pages fail in ways that destroy more than they should. A failed draft-generation poll on
WikiDraftReviewonly handles the "not found" case — a 500 or a dropped connection is swallowed entirely, leaving the GM staring at "Generating draft…" forever with no error and no timeout. OnWikiArticle, a failed version-history fetch or a failed version restore both call the page-level error setter, which replaces the entire rendered article with a bare red string — so a failed restore destroys the GM's view of the article they were trying to restore.CampaignStoryline's "Full regenerate" is destructive and has no confirmation dialog, unlike comparable destructive actions elsewhere in the same file (WikiArticle.jsx:1570,WikiProposals.jsx:234), and its "Append chapter"/"Full regenerate" actions tell the user to "refresh in a moment" with no polling, no progress, and no completion signal on one of the longest LLM jobs in the product.Evidence
webapp/frontend/src/pages/WikiDraftReview.jsx:244-249— the poll's catch only navigates on a "not found" error string; any other error (500, network drop) is swallowed and the page stays on "Generating draft…" indefinitely.webapp/frontend/src/pages/WikiArticle.jsx:1512,1537—fetchLoreVersionsandhandleRestoreVersionfailures both call the page-levelsetError, replacing the whole article with a bare red string.webapp/frontend/src/pages/CampaignStoryline.jsx:354,363— "Append chapter"/"Full regenerate" show "Working…" then "Chapter append queued — refresh in a moment.", with no polling, progress, or completion signal.webapp/frontend/src/pages/CampaignStoryline.jsx— "Full regenerate" is destructive with noconfirm(), unlikeWikiArticle.jsx:1570andWikiProposals.jsx:234in the same feature area.webapp/frontend/src/pages/CampaignStoryline.jsx:179—loadMoreEntriesfailure also calls page-levelsetError, replacing the entire rendered storyline.Why it matters for a hosted product
A page-level error that nukes the whole article on a failed restore is the worst possible moment for that pattern — it takes away the one thing (the current article content) the GM needed to see to decide what to do next.
Proposed fix
Add a timeout and a generic-error branch to
WikiDraftReview's poll catch, alongside its existing "not found" handling. ChangefetchLoreVersions/handleRestoreVersion/loadMoreEntriesfailures to inline errors next to the failing control, not page-levelsetError. Add aconfirm()to "Full regenerate", matching the pattern already used correctly elsewhere in the same file family. This is the audit's P25 (partial), P26, and P31.Acceptance criteria
WikiDraftReview's generation poll times out and shows an error state for non-"not found" failures, instead of polling forever.fetchLoreVersionsandhandleRestoreVersionfailures show an inline error without replacing the rendered article.loadMoreEntriesfailure shows an inline error without replacing the rendered storyline.CampaignStorylinerequires confirmation before running, matching the confirm pattern used elsewhere in the wiki.Picking this up as v4.3.0 phase 6 (#514). The draft-generation poll gets a timeout and a generic-error branch; version-history, restore and load-more failures become inline errors beside the failing control instead of replacing the whole article or storyline; "Full regenerate" gets the confirm its siblings already have.
Done in PR #527 (auto-merging on green); ships with v4.3.0.
The draft poll's only error branch was "not found", so a 500 or a dropped connection left "Generating draft…" up for as long as the tab stayed open. It now reports a failed check inline and keeps trying, gives up after 3 minutes, and offers Check again beside #416's reset. Three minutes sits between the banner's own "usually under 30 seconds" and the backend's 15-minute
DRAFT_STALE_AFTER, so a slow LLM call is never declared stuck.fetchLoreVersions,handleRestoreVersionandloadMoreEntriesall reported through the page-levelsetError, which unmounts the article (or the whole campaign story) and replaces it with one red string. Each is inline now, beside the control that failed; the history panel also stops asserting "No edit history yet" when what happened is that it could not read it. "Full regenerate" asks first, matching the three other destructive actions in this file family; "Append chapter" still does not, because it only adds.One gap left open deliberately: the generating banner (and hence Check again and reset) is inside the non-
convert_statslayout branch, so a stuck stat-conversion draft still cannot reach either control; the top status banner does now report the stall in every mode. That is #416's surface.9 new tests (the timeout test fakes only
Date, so the interval and the test library's waiting stay real). No backend change.For the record: the "poll gives up" test flaked again on the merge run for #538 (2026-09-06), despite PR #529's 20 s wait. The cause was an ordering race, not a timing margin: the test jumped the clock as soon as the page had rendered, but the poll's deadline is captured in a passive effect that can flush after that on a loaded runner, so the deadline itself moved and the banner could never appear. PR #541 waits for the first real poll tick before moving the clock. Test-only change; the behaviour this issue shipped is unchanged.