project-bifrost-platform/src/lib
Jonathan Hvid 5ddaad3da3 feat(vote): show percentages after voting + allow change-of-mind
The pulse vote widget on /pulse and on /dispatches/[slug] now behaves
the way the v5 spec asks:

- Eyebrow shortens: 'This week's pulse' → 'This week's'.
- Status line copy changes shape depending on whether the viewer has
  voted yet. Pre-vote: 'Vote to see how the council weighs in · Closes
  TUESDAY' — sets expectations that the percentages reveal after a
  vote. Post-vote: '2 of 7 voted · Closes TUESDAY · Click to change' —
  tells the viewer they can change their mind.
- Each option now renders a right-aligned tabular-nums percentage badge,
  but only after the viewer has voted. Pre-vote there's no percentage
  on screen at all — voting is a commitment, not a peek.
- Options stay clickable after voting (no `disabled`). Re-clicking a
  different option changes the vote.

DB: new helper castOrChangeVote(pulseId, userId, optionIndex) does an
UPSERT — INSERT on first vote, UPDATE option_index + voted_at on
subsequent. Returns true if this was the brand-new vote, so the caller
can write the 'voted' activity row exactly once and not double-count
changes-of-mind in the feed. castVote(...) (INSERT OR IGNORE) stays in
db.ts for callers that explicitly want first-vote-wins semantics.

Status-class rename: .locked → .closed on both pulse-option and
inline-poll-option. The class now reflects what it actually represents
(the pulse is closed) rather than the false invariant 'the user has
voted and can't change'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:20:16 +02:00
..
auth.ts feat: database schema, migrations, and seed data 2026-04-18 22:43:16 +02:00
db.ts feat(vote): show percentages after voting + allow change-of-mind 2026-05-12 11:20:16 +02:00
format.ts feat(pulse): two-column greeting + tenure-milestone copy 2026-05-12 10:48:50 +02:00
markdown.ts feat: database schema, migrations, and seed data 2026-04-18 22:43:16 +02:00
notify.ts feat(lib): notify() stub for pulse-opened events 2026-05-11 14:53:40 +02:00
routing.ts test: vitest suite — pulse status, vote uniqueness, home route 2026-05-11 14:45:55 +02:00