Skip to content

feat(spec-detail): surface πŸ‘/πŸ‘Ž quick vote top-left over the plot - #8126

Closed
MarkusNeusinger wants to merge 2 commits into
mainfrom
claude/thumb-vote-button-placement-UGjDr
Closed

feat(spec-detail): surface πŸ‘/πŸ‘Ž quick vote top-left over the plot#8126
MarkusNeusinger wants to merge 2 commits into
mainfrom
claude/thumb-vote-button-placement-UGjDr

Conversation

@MarkusNeusinger

@MarkusNeusinger MarkusNeusinger commented May 31, 2026

Copy link
Copy Markdown
Owner

Why

A visitor left a feature request on /campbell-basic/python/altair:

The thumbs-up / thumbs-down button should sit top-left over the plot in the detail views, instead of the report button β€” so people notice it right away and, hopefully, more of them tap πŸ‘ / πŸ‘Ž directly.

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 a report link (and hub mode keeps a report issue β†— link), so the in-plot flag was redundant.

What changed

  • Quick vote over the plot β€” SpecDetailView now 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 footer report link and the hub-mode report issue β†— link, so no entry point is lost. The reportUrl / onReport props are dropped from SpecDetailView.
  • Shared, DRY plumbing β€” new useQuickReaction hook posts the reaction reusing the exact payload shape + session correlation + analytics the floating FeedbackWidget already uses. Extracted the session key/id generator into utils/feedback.ts and a specIdFromPath parser into utils/paths.ts; FeedbackWidget now consumes both instead of its local duplicates.
  • Resilience β€” a failed submit (non-OK or network error) rolls the highlight back. The vote resets per implementation as the library carousel changes, so a highlight never bleeds across libraries.
  • Analytics β€” the new taps fire the existing feedback_submitted event with mode: "plot_overlay" (documented in docs/reference/plausible.md). The backend already accepts thumbs_up / thumbs_down, so no API change.

Tests

  • SpecDetailView: vote buttons render, a tap submits thumbs_up and marks the button pressed, a failed submit rolls back, and the overlay no longer exposes a report action.
  • New direct unit tests for paths.specIdFromPath and the feedback session-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

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.
Copilot AI review requested due to automatic review settings May 31, 2026 09:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, newFeedbackSessionId into shared utils and consume them from both FeedbackWidget and the new useQuickReaction hook.
  • Document the new plot_overlay mode in docs/reference/plausible.md and add SpecDetailView tests 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

codecov Bot commented May 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.95918% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
app/src/hooks/useQuickReaction.ts 93.75% 1 Missing ⚠️

πŸ“’ 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.

Copy link
Copy Markdown
Owner Author

Superseded by #11827, which re-implements this on the current main (the files moved to app/src/sections/spec-detail/ since May) with the placement the owner settled on β€” πŸ‘ top-left, πŸ‘Ž bottom-left β€” plus library_id/language on the feedback row so votes can be counted per image, and a one-vote-per-image-per-session lock. Closing this one.


Generated by Claude Code

MarkusNeusinger added a commit that referenced this pull request Sep 10, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants