docs: add sticker visibility toggle spec, plan, and version bump

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-23 17:17:52 -04:00
parent ab973cbf4f
commit 8a37b674fe
4 changed files with 551 additions and 1 deletions

View File

@@ -0,0 +1,122 @@
# Sticker Visibility Toggle — Design Spec
## Overview
Add a 3-way segmented toggle to the Picker's Game Session screen that lets admins control sticker layer visibility on the downstream vote-app overlay. The three modes are **Auto** (default), **Show**, and **Hide**, matching the downstream API contract defined in `docs/external-downstream-stickers.md`.
## Motivation
The vote-app overlay shows stickers that voters purchase during a session. By default, stickers follow poll state (visible during polls, hidden between). Admins need the ability to override this — force-showing stickers between rounds for a "sticker wall showcase" or force-hiding them during a busy poll for a cleaner view.
## Modes
| Mode | Behavior |
|------|----------|
| `auto` | Default — stickers visible during polls, hidden between polls |
| `show` | Force stickers visible regardless of poll state |
| `hide` | Force stickers hidden regardless of poll state |
When a new poll starts, the downstream vote-app auto-resets mode to `auto` and sends a `stickers.mode` event.
## Architecture
### State ownership
- `stickerMode` state (`useState('auto')`) lives in the `Picker` component, where the toggle UI renders.
- A `stickerSendRef` ref is created in `Picker` and passed to `SessionInfo`, which populates it with a send function once the WebSocket connection is authenticated.
### Props
`SessionInfo` receives two new props (following the existing pattern for `setPollActive`, `setLeadingGame`, etc.):
- `setStickerMode` — setter called when inbound `stickers.mode` WS events arrive.
- `stickerSendRef` — ref that SessionInfo populates with `(mode) => ws.send(...)`.
### WebSocket integration
All communication uses the existing `/api/sessions/live` WebSocket connection managed by `SessionInfo.connectWs`.
**Receiving (`stickers.mode` events):**
```javascript
if (message.type === 'stickers.mode') {
setStickerMode(message.mode);
return;
}
```
Added alongside the existing `poll.*` and `game.*` handlers in the `ws.onmessage` callback.
**Sending (`stickers.setMode` commands):**
After successful auth and subscribe, SessionInfo populates the ref:
```javascript
stickerSendRef.current = (mode) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'stickers.setMode', data: { mode } }));
}
};
```
The ref is cleared on disconnect/cleanup.
**No HTTP fallback.** The default `auto` state is correct on fresh load, and the WS connection handles all sync from that point.
## UI Design
### Placement
The sticker toggle renders in Picker's right column (`md:col-span-2`), **above** the poll leader indicator and poll control cards. It is always visible when an active session exists, regardless of poll state.
```
┌─────────────────────────────────────────────┐
│ Stickers [Auto] [Show] [Hide] │
└─────────────────────────────────────────────┘
↕ poll leader indicator (conditional)
↕ currently playing card (conditional)
↕ poll control card (conditional)
↕ SessionInfo games list
```
### Control style
A compact single-row card with:
- **Label** ("Stickers") on the left.
- **3-way segmented control** on the right, matching the Drawing/Family Friendly filter pattern (three adjacent buttons, active one highlighted).
### Segment colors
| Segment | Active background |
|---------|-------------------|
| Auto | Neutral gray (`bg-gray-500`) |
| Show | Green (`bg-green-500`) |
| Hide | Red (`bg-red-500`) |
Inactive segments use a muted/transparent style with border, same as the existing filter toggles.
### Auto-reset pulse
When a `stickers.mode` event arrives with `mode: "auto"` and the current local state is not already `auto`, a brief CSS pulse animation plays on the toggle card:
- A gentle border/background highlight flash that fades over ~2 seconds.
- Implemented via a CSS `@keyframes` animation toggled by a transient state flag.
- The flag is set on receiving an auto-reset event and cleared after the animation completes.
### Interaction behavior
- Clicking a segment calls `stickerSendRef.current(mode)` and optimistically updates local state.
- If the subsequent `stickers.mode` event returns a different value, state corrects to match.
- The toggle is visually disabled (reduced opacity, no pointer events) if the WebSocket is not connected.
## Files changed
| File | Change |
|------|--------|
| `frontend/src/pages/Picker.jsx` | Add `stickerMode` state, `stickerSendRef` ref, render sticker toggle card, pass new props to `SessionInfo` |
| `frontend/src/pages/Picker.jsx` (`SessionInfo`) | Accept `setStickerMode` and `stickerSendRef` props, handle `stickers.mode` WS events, populate send ref |
## Scope boundaries
- **In scope:** Frontend toggle UI + WS message send/receive in the game picker.
- **Out of scope:** Backend relay of `stickers.setMode` messages to the vote-app (assumed handled by existing infrastructure or a separate task). Vote-app sticker rendering (already implemented downstream).