Add citation repair/insertion action row to the Citations tab (PRD-0014)#109
Conversation
… PRD-0008 PRD-0014 designs the Citations tab's first write affordance: a per-finding action row for Partial/NotSupported verdicts that routes to either the existing InlineEdit action (fix the article text) or PRD-0008/0012's propose/confirm flows (fix the citation), with the operator always choosing which -- SP42 makes no judgment about which side of a mismatch is wrong. PRD-0012 (citation insertion) had been drafted but never committed; landing it here since PRD-0014 depends on its insert flow. State set to Discussion, not Accepted -- its open questions are still proposed answers, not resolved. PRD-0008 (bare-URL repair) shipped in PR #40 but its State field was never updated. Flipped to Accepted: all 5 open questions already carried decided answers, and DoD items 1-7 are backed by the existing test suite (named inline). DoD item 8 (a live confirmed repair on test.wikipedia.org) was left unchecked in PR #40's own test plan as a manual step -- not moved to Implemented pending confirmation that step has run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Section-by-section review added a third action (Flag citation, generalizing TagCitationNeeded to a small CitationConcernKind set: FailedVerification for NotSupported, PartialSupport for Partial -- both real enwiki templates, verdict-suggested but operator-confirmed, never auto-applied) and a fourth control (Re-verify, a thin new route over the existing verify_citation_use_site primitive, letting a finding re-check itself without a full page rescan). All five open questions resolved: repair-first sequencing for Fix citation (tracked in #108); Re-verify instead of relying on a stale card; whole-sentence InlineEdit pre-fill; the NotSupported flag anchor rides on PRD-0012's spawned ADR rather than a second one; no restriction on template override. State Draft -> Accepted. FrWiki template mapping deliberately deferred -- no clean 1:1 equivalent to {{Failed verification}} exists there, and picking one unilaterally risks the same community-classification concern PRD-0008 already flags elsewhere. Survey tracked in #107, frwiki-specific finding in its comments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…0014) Generalizes TagCitationNeeded into a small closed CitationConcernKind set (PartialSupport, FailedVerification), each mapped to a per-wiki maintenance template via WikiTemplates.citation_concerns. Adds the SessionActionKind::FlagCitation executor for the PartialSupport span-wrap path (reuses the existing exactly-one-occurrence literal guard) with an optional operator reason threaded into the template's reason= parameter. FailedVerification's insert-after-<ref> anchor has no primitive yet, so it refuses informatively rather than doing nothing, matching the PRD's own repair-first sequencing. Configures partial-support on test.wikipedia.org (configs/testwiki.yaml).
Wraps verify_citation_use_site for a single finding instead of a full page rescan, so re-checking whether an edit resolved a mismatch costs one use-site of inference, not every citation on the page. Reuses verify_one_use_site (page.rs's own per-use-site fan-out primitive, including its archive-fallback handling) unchanged — the route adds no new grounding/panel/verdict logic. Session+CSRF-gated like verify-page, since it spends the operator's inference budget; refuses with ref-not-found (404) when the ref no longer exists on the current revision.
Adds a per-finding action row for Partial/NotSupported citation findings: Edit article text (InlineEdit, empty operator-authored textarea per DoD — SP42 never suggests replacement text), Fix citation (routes to PRD-0008's bare-URL repair replace flow when the finding has an existing ref, or an informative "not shipped yet" message when it doesn't — PRD-0012 isn't implemented), Flag citation (verdict-suggested concern kind, operator override, optional reason), and an always-available Re-verify that replaces the card in place with the fresh result. None of the four is pre-selected; the evidence disclosure now defaults open when the row is shown, so the claim and located passage render side by side. This is the first browser call site for PRD-0008's bare-URL propose/apply routes — previously CLI/server-only. Adds three new sp42-app/platform/citation.rs functions (reverify_finding, fetch_bare_url_proposals, apply_bare_url_proposal) mirroring the existing fetch_page_report wasm/native-stub split. Pure routing/gating logic (which verdicts get a row, which concern kind is suggested, replace-vs-insert routing) is unit tested. Full component tests aren't possible — this repo has no Leptos DOM test harness — and sp42-app's pages/components/platform modules are wasm32-gated at the crate root, so even these pure-logic tests don't run under the workspace's plain `cargo test` CI invocation; both are pre-existing characteristics of this crate, not introduced here.
Pulls the "operator selection wins over SP42's suggestion" logic out of the Confirm-flag click handler into resolve_concern_kind, so the DoD's override-precedence requirement is covered by a real test instead of only living inside an unreachable-by-cargo-test closure.
Checks off what shipped this pass, leaves the rest unchecked with a status note: no side-by-side evidence layout (stacked-but-expanded instead), no proactive "no configured template" UI message, no wiki-level UI gating, and — most importantly — the live-edit acceptance gate itself is still open, pending an authenticated test.wikipedia.org session.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 81c59b03ea
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let Some(use_site) = extract | ||
| .use_sites | ||
| .into_iter() | ||
| .find(|use_site| use_site.ref_id == payload.ref_id) |
There was a problem hiding this comment.
Match reverify requests to the specific use-site
When a single reference yields multiple citation use-sites (for example, a citation template with multiple source URLs), the report can contain multiple findings with the same ref_id. This route only matches on ref_id and takes the first extracted use-site, so clicking Re-verify on a later URL's card verifies the wrong source and replaces the card with another finding. Include the source URL or use-site ordinal in the request before selecting the use-site.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 09be5f7. ReverifyFindingRequest gains an optional use_site_ordinal; the route selects the use-site by ordinal when it's set (falling back to the first ref_id match for back-compat), and the browser now sends the card's ordinal so a ref with multiple source URLs re-verifies the right one.
Generated by Claude Code
| Ok(response) => { | ||
| finding.set(response.finding); |
There was a problem hiding this comment.
Track the reverified revision for later applies
When the original report was produced for revision N and Re-verify fetches the latest revision N+1 (rev_id: 0 in the request), this discards response.rev_id and only updates the finding. All later Edit/Fix/Flag requests from the same row still use the captured old rev_id as baserevid, so after a successful reverify against current article state, the next confirmed action can fail with an edit conflict or be anchored to the wrong revision. Store the returned revision alongside the finding and use it for subsequent action payloads.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 09be5f7. The action row now holds a base_rev_id signal seeded from the report; a successful Re-verify advances it to the returned rev_id, and the Edit/Fix/Flag payloads read from it — so a confirmed action after re-verify anchors to the revision actually verified rather than the stale captured one.
Generated by Claude Code
| if let Some(found) = response | ||
| .proposals | ||
| .into_iter() | ||
| .find(|p| p.url == source_url_for_load) |
There was a problem hiding this comment.
Disambiguate duplicate bare-URL repair proposals
If the page has two different references to the same bare URL, the proposals endpoint returns a proposal for each reference, but this selects the first proposal whose URL matches the finding's source URL. Confirming repair from the second citation's card can therefore apply the locator for the first citation instead of the ref being reviewed. Match on a ref-specific locator/ordinal or refuse when the URL is not unique.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 09be5f7. The finding identifies only the source URL, and the proposal's locator ordinal is a wikitext-node ordinal (not the finding's use-site ordinal), so there's no reliable shared key to disambiguate. Per your second suggestion, Fix citation now proceeds only when exactly one proposal matches the URL; when two+ references cite the same bare URL it refuses and points at the locator-addressed CLI rather than applying the wrong ref's locator.
Generated by Claude Code
…ion, refuse ambiguous repair Three review findings on the Citations action row (PRD-0014), all about a page with duplicate references or advancing revisions: - Re-verify matched only on ref_id and took the first extracted use-site, so a ref with several source URLs re-verified the wrong source. Add an optional use_site_ordinal to ReverifyFindingRequest; the route pins the exact use-site when it's set (back-compatible fallback to first ref match), and the browser sends the card's ordinal. - A successful Re-verify runs against the latest revision but its returned rev_id was discarded, leaving later Edit/Fix/Flag actions anchored to the stale baserevid (edit conflict / wrong-revision risk). The action row now tracks a base_rev_id signal that Re-verify advances to the verified revision, and the three action payloads read from it. - Fix citation selected the first bare-URL proposal whose URL matched the finding, so with two references to the same bare URL it could apply the wrong ref's locator. The finding identifies only the source URL, so when the match is not unique the panel now refuses and points at the locator-addressed CLI instead of guessing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MT5gvrNTp77JJqRTC5bKov
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09be5f7843
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let Some(use_site) = extract.use_sites.into_iter().find(|use_site| { | ||
| use_site.ref_id == payload.ref_id | ||
| && payload | ||
| .use_site_ordinal | ||
| .is_none_or(|ordinal| use_site.use_site_ordinal == ordinal) |
There was a problem hiding this comment.
Match reverify by a stable use-site identifier
When Re-verify runs against the latest revision, this compares the stored use_site_ordinal from the old report to the newly extracted page-global ordinal. extract_use_sites assigns that ordinal by document order across the whole page, so adding/removing any earlier citation (or inserting another URL earlier in the same ref) shifts it: the same ref_id can then 404 as ref-not-found, or in a multi-URL ref select a different source. Include the original source URL or a ref-local source index in the request instead of relying on the page-global ordinal across revisions.
Useful? React with 👍 / 👎.
…er-surface # Conflicts: # crates/sp42-server/src/tests.rs # docs/domains/references/README.md
The design-system gate (added to main after PRD-0014 was written, alongside the sp42-ui extraction) forbids raw DOM tags in sp42-app presentation code. Replace the four `<div class="sp42-citation-*">` wrappers in the Citations action row, flag panel, and fix panel with `Stack` primitives (the classes carried no CSS — they were unstyled structural wrappers, so this is layout-neutral), and reword two doc comments so their prose no longer trips the raw-DOM-tag check. No behavior change; design-system, layering, forbidden-pattern, fmt, clippy (workspace), and all tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MT5gvrNTp77JJqRTC5bKov
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 639b73fca6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Advance the baserevid for later actions to the | ||
| // revision we actually verified against. | ||
| set_base_rev_id.set(response.rev_id); | ||
| finding.set(response.finding); |
There was a problem hiding this comment.
Regroup cards after reverify changes verdict
When Re-verify returns a different verdict, this only replaces the card's local finding signal; the enclosing FindingGroupSection header/count/color was computed from the original report, and FindingCard relies on that group to communicate the verdict. For example, a NotSupported finding that re-verifies as Supported will still be displayed under the old “Not supported” section, which can mislead the operator. Update the page-level report/grouping after reverify, or render the refreshed verdict directly in the card.
Useful? React with 👍 / 👎.
…rface PRD-0014's per-finding action row (edit / fix / flag / re-verify controls plus their inline panels) adds ~6 KiB gzip to the optimized bundle, pushing it just past the 832 KiB gzip ceiling (857,896 > 851,968). Ratchet the gzip ceiling up to 848 KiB (868,352 bytes, ~10 KiB headroom) as a recorded decision under Constitution Art. 5.2, mirroring the earlier raise for the citation-review read surface. Raw size is unchanged and already under its ceiling; ratchet back down as the bundle shrinks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MT5gvrNTp77JJqRTC5bKov
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1dc0d3b656
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let mut matching = response | ||
| .proposals | ||
| .into_iter() | ||
| .filter(|p| p.url == source_url_for_load); | ||
| match (matching.next(), matching.next()) { |
There was a problem hiding this comment.
Require ref identity before applying a matching proposal
When the finding's own citation is not a bare URL but another reference on the page cites the same URL as a bare URL, this filter still yields exactly one proposal and the (Some(found), None) branch will apply that other reference's locator. Uniqueness among bare-URL proposals does not prove the proposal belongs to this card's ref_id, because non-bare refs with the same source URL are excluded from response.proposals and proposals carry no ref id. Include the originating ref identity in the proposal match, or only offer this branch when the finding's ref is known to be the bare-URL node.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in c3c47c4. Findings now carry is_bare_url_ref, classified in the Parsoid extractor from the ref's rendered content (the same classify_bare_url test the repair uses), and threaded BlockRef → CitationUseSite → CitationFinding. fix_citation_route returns a new AlreadyFormatted route for a non-bare ref, so the URL-matching filter you flagged is unreachable for exactly the case you describe — the finding's own ref is a formatted citation while another ref cites the same URL as bare. A genuinely-bare finding still matches by URL and refuses when >1 bare proposal shares it. Verified on real Parsoid HTML fixtures (cite-template → not bare, bare-ExtLink → bare) plus 3-way routing tests.
Generated by Claude Code
PRD-0012 (citation insertion) and PRD-0014 (citation repair browser surface) are claimed on the citation-repair-browser-surface branch (PR #109), and PRD-0013 (citation write surface) on citation-write-surface (PR #106). 0015 is the first number free across all remote branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATEQwdt8J9mVb94EcgRFY3
Corrects the appendix-posting step: appending to Talk:Article/GAn is an insert-at-position, so it depends on PRD-0012's spawned ADR-0003 insertion extension (or PRD-0013's guarded-edit Insert op) plus ADR-0018's token seam for non-server shells, not on shipped ADR-0010 mechanism alone. Marks the hold loop's re-verify dependency as PRD-0014's route (PR #109). Adopts PRD-0014's mismatch framing for report wording, and adds an in-flight dependencies section covering PRD-0012/0013/0014 and the ADR-0016 PRs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATEQwdt8J9mVb94EcgRFY3
…full re-group Comprehensive fixes for three Codex P2 review findings on #109, all rooted in imprecise finding/use-site/proposal identity: 1. Re-verify keyed on a stable cross-revision identity. The page-global use_site_ordinal shifts when an earlier citation changes, and re-verify runs against the latest revision — so matching by ordinal could 404 or pick the wrong source. ReverifyFindingRequest now carries the cited source_url; the route selects the use-site by (ref_id, source_url) via a new pure select_reverify_use_site (ordinal kept only as a positional fallback). The browser sends archive_of.unwrap_or(provenance.url) — the original cited URL. 2. Bare-URL repair offered only when the finding's OWN ref is bare. Proposals exclude non-bare refs and carry no ref id, so matching by shared URL could apply another ref's locator to a formatted citation. The parsoid extractor now classifies each ref's rendered content as a bare URL (is_bare_url_ref, threaded BlockRef -> CitationUseSite -> CitationFinding); "Fix citation" routes to bare-URL repair only for a genuinely bare ref, and reports an already-formatted ref informatively instead of guessing. 3. Full re-group after a verdict-changing re-verify. Re-verify now folds the fresh finding into the page report (new apply_reverified_finding replaces the finding and recomputes the verdict tallies), so a NotSupported finding that re-verifies to Supported moves to the Supported section with counts/colors updated, instead of lingering under the old header. Wired via a page-level context callback. Tests: select_reverify_use_site (ordinal-shift stability), parsoid bareness classification on real Parsoid HTML fixtures, fix_citation_route 3-way routing, and apply_reverified_finding (verdict move + tally recompute). All workspace tests, native clippy, fmt, design-system, layering, and forbidden-pattern gates pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MT5gvrNTp77JJqRTC5bKov
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3c47c4158
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| Some(apply) => apply.0.run(( | ||
| apply_ref_id.clone(), | ||
| apply_ordinal, | ||
| response.finding, | ||
| )), |
There was a problem hiding this comment.
Preserve the reverified revision in the report
When a report was loaded for revision N and Re-verify runs against the latest revision N+1, this path only passes the fresh finding into the page-level update while dropping response.rev_id. The UI still renders the report header/raw report and each show citation in article link from report.rev_id, so the refreshed finding is displayed and linked as if it were verified on the old revision even though its claim/verdict came from N+1. Include the returned revision in the report/card state (or otherwise mark the card as mixed-revision) when applying a reverify result.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — this is a real consequence of folding an N+1 verdict into an N report. For #109 we're keeping the one-finding fold-and-regroup and carrying this as a known interim limitation. The proper fix is to make Re-verify refresh the whole page to the current revision (so header, links, counts, and every card describe one revision), which only stays cheap with an in-memory verdict cache. That's now designed: PRD-0014's 2026-07-09 amendment + ADR-0019 (Cached page re-verification), on docs/cached-page-reverification. Tracking the display fix there rather than adding per-card revision bookkeeping to this PR.
Generated by Claude Code
Brings in the citation repair/insertion browser surface (#109), the architecture map (#118), docs attic move, and PRD-0012/0014/0015. Conflict resolved in scripts/check-wasm-size.sh: both sides recorded a gzip-ceiling decision (main's PRD-0014 action-row surface at 848 KiB, this branch's entity-diff surface at 860 KiB). The merged bundle carries both surfaces, so the ceiling keeps the larger 860 KiB with both decisions recorded in the comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012u5MVp3vAN44KhsVg3vroY
Picks up PRD-0012/PRD-0014 (landed via #109) and PRD-0008's Accepted state (set upstream in 1f4b3d7, preserved through the rebase). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWVy56w8xjBRQKwC94Har3
Review catch: the DoD item explicitly requires a reachable-paywalled fixture with human_readable_hint = true, and probe.rs only exercised the anti-bot class. New test drives a NavChromePaywall fixture (paywall vendor script + nav chrome, no prose) through probe_source. Also fixes sp42-mcp test compilation, broken on main since is_bare_url_ref was added to BlockRef (#109) without updating the page.rs fixture — CI missed it because check-focused.sh does not cover sp42-mcp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWVy56w8xjBRQKwC94Har3
What and why
The Citations tab shows verification findings but you can't act on them —
every citation write today is CLI-only. This adds a per-finding action row
for
Partial/NotSupportedfindings with three equal-weight paths: fix thecitation, fix the article text, or flag it for a reviewer with more context.
A mismatch can mean either side is wrong, so SP42 doesn't guess — the
operator picks. Full design in PRD-0014.
Also adds an always-available Re-verify control that re-checks one
finding in place, so confirming a fix worked doesn't require a full-page
rescan.
What's in this PR
CitationConcernKind(partial-support / failed-verification), generalizingthe existing
TagCitationNeededtemplate mechanism to a small closed set.partial-support(span-wrap) ships working;failed-verification(insert-after-
<ref>) refuses informatively — no primitive exists for ityet (see follow-up issue).
/dev/citation/reverifyroute, reusing the existing per-use-siteverifier unchanged — one finding's worth of inference, not a full rescan.
PRD-0008's bare-URL repair when there's an existing ref; an informative
message when there isn't, since PRD-0012 hasn't shipped), flag citation
(suggested template, operator override, optional reason), re-verify.
no UI, CLI only.
What's explicitly not in this PR
confirmed
Partialflag landing ontest.wikipedia.org, driven throughthe browser, before this is really "done." I don't have a Wikimedia
session in the environment this was built in — someone needs to click
through it for real.
failed-verification(NotSupported) flag path — needs a newinsert-at-position primitive. Filed as a follow-up.
side by side.
configured template — the refusal surfaces reactively after confirming,
not before.
PRD-0014's Definition of Done is updated to reflect exactly what's checked
off vs. open, with the reasoning inline.
Test plan
cargo test --workspace— all green (server + citation crates fully;app-crate logic tests compile and were traced by hand, since
sp42-app'sbrowser-only modules are wasm32-gated and don't run under the workspace's
plain test command — pre-existing repo characteristic, not new).
cargo clippy --workspace --all-targets --all-features -- -D warnings—clean.
browser session; couldn't get past the login gate without credentials to
visually confirm the action row itself.
🤖 Generated with Claude Code