RE /next: per-answer feedback, and a disagreement lands in the gaps collection - #117
Merged
Merged
Conversation
…ollection PLAN-FINISH-REPOS.md item 8. Feedback existed twice — a floating product button and chat-turn votes — and neither was about an answer to a catalogued question, which is the surface a curator actually reads. Adds a "Was this right? · Right · Partly · Wrong" control under every answered question row, and POST /api/feedback/answer behind it. A disagreement becomes a gap about the ANALYSIS, marked `ours` — destinations.py's rule 1: the repository is not at fault for our answer about it. The control lives in next/feedback.js and does not touch app.js: that file is being split into per-stage modules, and it rewrites a row's innerHTML in place as each answer lands, so the bar is re-attached by a MutationObserver and idempotence is keyed on the bar rather than a flag on the row (which would survive the row's own innerHTML being replaced and stop it coming back). The route resolves which analyses answer a question from the question catalog rather than trusting the client, and rejects an analysis_id the catalog does not list for it — a stale page must not be able to charge a dispute to an analysis that never answered. Every verdict is stored, not only disagreements: "nobody has questioned this" and "someone confirmed it" must not look alike. A question naming no analysis records analysis_id "" rather than a guessed one. Gap identity is (slug, analysis_id, disagreement, "question:<text>"), so a second person disagreeing refreshes one row — the count stays "answers disputed", not "clicks" — and the question: prefix cannot collide with a check_registry.yaml check name, so record_gaps_for leaves it standing. Gap rows now state destination `ours` and source measured|person, with a disputed_by_a_person count that is a subset of the disagreement count. Not verified on screen: :8810 serves from the main checkout, which is on main. The two fragile points are named in the design note. 13 new tests; full suite 4762 passed, 1 pre-existing failure in test_fact_answer_rendering.py (a byte-distance assertion on app.js, which this branch does not modify). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PLAN-FINISH-REPOS.md item 8 — Feedback: present, not used. Done test: per-answer feedback exists, and a disagreement lands in the gaps collection as destination
ours.What this adds
next/feedback.js— "Was this right? · Right · Partly · Wrong" under every answered question row. Noapp.jsedit: a MutationObserver on#question-rowsplus delegated clicks, so it survives the app.js split and re-attaches afterreplaceRowrewrites a row's innerHTML. Idempotence is keyed on the injected bar, not a flag on the row.POST /api/feedback/answer— resolves which analyses answer the question from the catalog rather than trusting the client; rejects ananalysis_idthe catalog does not list for it. Every verdict is stored; onlydisagreeraises a gap.gaps.py: record_disagreement— sameupsert_gapas the measured gaps, markedours. Identity(slug, analysis_id, disagreement, question:<text>), so two people disagreeing is one row. Gap rows now carrydestination: oursandsource: measured | person, plus adisputed_by_a_personcount (a subset of the disagreement count).docs/design-notes/ITEM-8-FEEDBACK-IMPLEMENTED.md— what it replies to, what was scoped out, what could not be tested.Not verified on screen. :8810 serves from the main checkout on
main; the control has not been seen rendering. First things to check after fast-forward: the[data-evidence]row selector and re-attachment afterreplaceRow.Tests. 13 new (
test_gaps.py::TestAPersonsDisagreement,test_answer_feedback_gap.py). Full suite 4762 passed; the one failure (test_fact_answer_rendering.py, an index.html byte-window assertion) is pre-existing and fixed in #115/#116.🤖 Generated with Claude Code