123 lines
5.1 KiB
Markdown
123 lines
5.1 KiB
Markdown
# 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).
|