feat(spec-detail): surface π/π quick vote top-left over the plot - #8126
feat(spec-detail): surface π/π quick vote top-left over the plot#8126MarkusNeusinger wants to merge 2 commits into
Conversation
Move the prominent top-left overlay slot on spec detail plots from the .report() flag to a one-tap π / π quick vote, so visitors see and use it immediately (feature request on /campbell-basic/python/altair). The .report() flag relocates into the right-hand action cluster and stays fully accessible. - Add `useQuickReaction` hook posting a reaction-only entry to /feedback, reusing the same session correlation + analytics as the FeedbackWidget (tracked as `feedback_submitted` with `mode: "plot_overlay"`). - Extract shared feedback helpers (session key + id generator) and a `specIdFromPath` path parser; FeedbackWidget now consumes them instead of its local duplicates. - Optimistic highlight on tap, rolled back if the submit fails; vote resets per implementation as the library carousel changes. - Cover the new buttons (render, submit, rollback, relocated report) and document the new analytics mode.
There was a problem hiding this comment.
Pull request overview
Moves the prominent top-left plot-overlay slot in SpecDetailView from the .report() flag to a one-tap π/π quick vote, while keeping the report action available in the right-hand cluster. Shared session/spec-id helpers and a new useQuickReaction hook reuse the existing /feedback plumbing and feedback_submitted analytics event (new mode: "plot_overlay").
Changes:
- Add thumbs-up/down overlay buttons (with optimistic highlight + rollback on failure) to both the static preview and interactive iframe surfaces of
SpecDetailView; reset the vote when the library changes. - Extract
specIdFromPath,FEEDBACK_SESSION_KEY,newFeedbackSessionIdinto shared utils and consume them from bothFeedbackWidgetand the newuseQuickReactionhook. - Document the new
plot_overlaymode indocs/reference/plausible.mdand addSpecDetailViewtests for render/submit/rollback/report-link.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| docs/reference/plausible.md | Adds SpecDetailView.tsx source and plot_overlay mode to the feedback_submitted event docs. |
| app/src/utils/paths.ts | Adds reusable specIdFromPath helper. |
| app/src/utils/feedback.ts | New shared module: feedback session storage key, quick-reaction type, opaque session-id generator. |
| app/src/hooks/useQuickReaction.ts | New hook that POSTs a reaction-only /feedback entry and tracks feedback_submitted analytics. |
| app/src/components/SpecDetailView.tsx | Renders π/π overlay buttons top-left and relocates the report flag into the right action cluster on both surfaces. |
| app/src/components/SpecDetailView.test.tsx | Adds tests for vote rendering, submit-success state, submit-failure rollback, and the relocated report link. |
| app/src/components/FeedbackWidget.tsx | Switches to the shared session key, session-id generator, and specIdFromPath. |
Codecov Reportβ Patch coverage is
π’ Thoughts on this report? Let us know! |
Per review feedback, remove the .report() flag from the plot overlay entirely β the footer's report link (and the hub-mode "report issue β" link) already cover that path, so the overlay flag was redundant. The π/π quick vote now owns both the top-left corner and the right cluster stays focused on copy/download/open. Also add direct unit tests for the new pure helpers (specIdFromPath plus the feedback session-id generator and its Web Crypto fallbacks) to lift patch coverage flagged by codecov.
|
Superseded by #11827, which re-implements this on the current Generated by Claude Code |
β¦r image (#11827) ## Summary - **π / π directly on the plot.** The spec detail view now renders a thumbs-up button top-left and a thumbs-down button bottom-left over the image, on both the static preview and the interactive surface, in place of the `.report()` flag (reporting stays on the hub page link and in the footer). One tap inks the thumb (brand green / matte red), flashes `>>> .liked` / `>>> .disliked` in the centre like `.copied`, and posts a reaction-only entry to `/feedback` naming the implementation. - **Votes are countable per image.** New Alembic revision `a7c3e9d1f5b8` adds `feedback.library_id` + `feedback.language` and a `(spec_id, library_id)` index; `FeedbackRepository.reaction_counts(spec_id, library_id, language?)` returns `{thumbs_up, thumbs_down}` counting the newest row per session (ordered by `created_at`, then `id`, so ties resolve the same way every run). Nothing is displayed yet β this is the seam for a later "show counts" step. - **One vote per image per session.** The choice is kept in localStorage (`anyplot_plot_votes`) so the thumb stays inked across reloads and carousel switches, the other thumb is natively `disabled` (faded, tooltip `.rated()` on a wrapping span), and the server silently drops a repeat π/π from the same session for the same implementation (`FeedbackRepository.has_plot_vote`). A vote is final; nobody can flip up and down at will. - **Reaction-only rate limit.** Free text stays at 5/min per IP, now counted over message-bearing rows only; a π/π tap is capped at 30/min, so rating several plots in a row neither trips the free-text limit nor blocks a message written afterwards. - Shared feedback helpers (`FEEDBACK_SESSION_KEY`, `newFeedbackSessionId`, `specIdFromPath`) move out of `FeedbackWidget` into `utils/feedback.ts` and `routes/paths.ts`; the widget keeps its behaviour (page-level reactions carry no `library_id`, so they never count as plot votes). Supersedes #8126 (both thumbs top-left, no per-library field, three months behind `main` with the files since moved). Placement per owner decision: π top-left, π bottom-left. ## Test plan - [x] `/verify-core`: `uv run pytest tests/unit tests/integration` β 2004 passed, 1 skipped (pre-existing MonoLisa italic skip); `ruff check` + `ruff format --check` + `mypy api core` clean. New tests: repository `has_plot_vote`, `reaction_counts` (newest-per-session, language filter, session-less rows), `count_recent_by_ip(messages_only=)`; router unit tests for the vote guard, the looser reaction limit and the page-level bypass; integration tests for persisted `library_id`/`language` and one-vote-per-session. - [x] `/verify-migrations`: throwaway Postgres 16 (pgserver) β `alembic heads` single head `a7c3e9d1f5b8`, `upgrade head`, `alembic check` "No new upgrade operations detected.", `downgrade -1` + `upgrade head` roundtrip clean. - [x] `/verify-frontend`: `yarn type-check`, `yarn lint` (0 warnings), Prettier clean, `yarn vitest run` β 71 files / 640 tests passed. Drove `/scatter-basic/python/matplotlib` in headless Chromium at 1280px and 390px, light and dark: both thumbs render, tap π β inked + toast + exactly one POST (`spec_id`, `library_id`, `language`, `session_id`), π locked, vote survives reload. The POST was stubbed locally; no row was written to the shared production DB. - [x] `uv run python -m tools.changelog check --base origin/main` passes. - [x] Copilot review round 1: three findings (native `disabled`, stable ranking tiebreaker, tz-naive test cutoff) applied in the second commit; threads resolved. ## Checklist - [x] `changelog.d/thumbs-on-plot.md` added (Added / Changed / Removed), no new bullet in `CHANGELOG.md` - [x] Related documentation updated: `docs/reference/plausible.md` (`feedback_submitted` mode `plot_overlay` + `library` prop, `report_issue` source), `docs/contributing.md` (where "report issue" now lives) **Deploy note:** the migration only adds nullable columns and an index; `sync-postgres.yml` runs `alembic upgrade head` after merge. The frontend sends `library_id`/`language` regardless; if the API deploys first, the extra fields are still accepted (they are optional in `FeedbackRequest`). π€ Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Eckdg6kWrjasZMwNn8A1v8 --------- Co-authored-by: Claude <noreply@anthropic.com>
Why
A visitor left a feature request on
/campbell-basic/python/altair:The prominent top-left overlay corner held the
.report()flag, which is the least likely action a casual visitor wants. This swaps that slot for a one-tap quick vote and removes the report flag from the overlay entirely β the footer already has areportlink (and hub mode keeps areport issue βlink), so the in-plot flag was redundant.What changed
SpecDetailViewnow renders π / πIconButtons in the top-left overlay (both the static preview and the interactive iframe surfaces). Tapping one optimistically inks the chosen thumb in the brand colour and posts a reaction-only entry to/feedback..report()removed from the overlay β reporting stays available via the footerreportlink and the hub-modereport issue βlink, so no entry point is lost. ThereportUrl/onReportprops are dropped fromSpecDetailView.useQuickReactionhook posts the reaction reusing the exact payload shape + session correlation + analytics the floatingFeedbackWidgetalready uses. Extracted the session key/id generator intoutils/feedback.tsand aspecIdFromPathparser intoutils/paths.ts;FeedbackWidgetnow consumes both instead of its local duplicates.feedback_submittedevent withmode: "plot_overlay"(documented indocs/reference/plausible.md). The backend already acceptsthumbs_up/thumbs_down, so no API change.Tests
SpecDetailView: vote buttons render, a tap submitsthumbs_upand marks the button pressed, a failed submit rolls back, and the overlay no longer exposes a report action.paths.specIdFromPathand thefeedbacksession-id helper (incl. its Web Crypto fallbacks).yarn type-check,yarn lint(no new warnings), and the full frontend suite all pass.https://claude.ai/code/session_01DGgHVGTCLdKvSFVAk5acDH