docs: add sticker visibility toggle spec, plan, and version bump
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
162
docs/external-downtream-stickers.md
Normal file
162
docs/external-downtream-stickers.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Sticker Layer Visibility Control
|
||||
|
||||
Integration guide for the Jackbox Game Picker to toggle sticker layer visibility on the vote-app overlay.
|
||||
|
||||
## Overview
|
||||
|
||||
The vote-app overlay shows stickers that voters purchase throughout a game session. By default, stickers are visible whenever a poll is displayed and hidden between polls. This API allows the Game Picker host to override that behavior — showing accumulated stickers between rounds (a "sticker wall showcase") or hiding them during a 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 |
|
||||
|
||||
**Auto-reset:** When a new poll is generated, mode automatically resets to `auto`. The Game Picker receives a notification so its UI can update.
|
||||
|
||||
## WebSocket Integration (Primary)
|
||||
|
||||
### Sending: Set Mode
|
||||
|
||||
Send on the existing upstream WebSocket connection (`/api/sessions/live`):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stickers.setMode",
|
||||
"data": {
|
||||
"mode": "show"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Valid values for `mode`: `"auto"`, `"show"`, `"hide"`.
|
||||
|
||||
### Receiving: Mode Changed
|
||||
|
||||
The vote-app sends this whenever the mode changes (including auto-resets):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stickers.mode",
|
||||
"mode": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
**When you'll receive this:**
|
||||
- After you send `stickers.setMode` (confirmation)
|
||||
- When a new poll starts (mode resets to `auto`)
|
||||
- When a session starts/ends (mode resets to `auto`)
|
||||
- When the debug panel changes the mode
|
||||
|
||||
**Use this to keep your UI in sync.** If you show a toggle button, update its state whenever you receive this message.
|
||||
|
||||
## HTTP API (Fallback)
|
||||
|
||||
### Set Mode
|
||||
|
||||
```
|
||||
PUT /api/stickers/visibility
|
||||
```
|
||||
|
||||
**Headers:**
|
||||
- `Content-Type: application/json`
|
||||
- `X-API-Key: <UPSTREAM_API_KEY>`
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{ "mode": "show" }
|
||||
```
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{ "mode": "show", "previousMode": "auto" }
|
||||
```
|
||||
|
||||
**Error responses:**
|
||||
- `403` — Invalid or missing API key
|
||||
- `400` — Invalid mode value (not `auto`/`show`/`hide`)
|
||||
|
||||
### Get Current Mode
|
||||
|
||||
```
|
||||
GET /api/stickers/visibility
|
||||
```
|
||||
|
||||
No auth required.
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{ "mode": "auto" }
|
||||
```
|
||||
|
||||
Use this on startup to sync your UI state.
|
||||
|
||||
## Suggested UX
|
||||
|
||||
### Simple Toggle Button
|
||||
|
||||
A two-state toggle that alternates between `show` and `auto`:
|
||||
|
||||
```
|
||||
[Show Stickers] ← when mode is "auto" or "hide"
|
||||
[Hide Stickers] ← when mode is "show"
|
||||
```
|
||||
|
||||
When receiving `stickers.mode` with `"auto"`, reset the button to "Show Stickers" state.
|
||||
|
||||
### Three-State Control (Advanced)
|
||||
|
||||
If you prefer explicit control:
|
||||
|
||||
```
|
||||
( ) Auto — stickers follow poll visibility
|
||||
(•) Show — always visible
|
||||
( ) Hide — always hidden
|
||||
```
|
||||
|
||||
Highlight the current selection. Update when receiving `stickers.mode`.
|
||||
|
||||
### Handling Auto-Reset
|
||||
|
||||
When a poll starts, mode resets and you'll receive:
|
||||
```json
|
||||
{ "type": "stickers.mode", "mode": "auto" }
|
||||
```
|
||||
|
||||
Your UI should:
|
||||
1. Update the button/toggle state to reflect `auto`
|
||||
2. Optionally show a brief indicator ("Sticker mode reset")
|
||||
|
||||
## Example Flows
|
||||
|
||||
### Show stickers between games
|
||||
|
||||
1. Game ends, poll results shown, winner selected
|
||||
2. Host wants to show off sticker wall before next game
|
||||
3. Host clicks "Show Stickers" → sends `stickers.setMode` with `mode: "show"`
|
||||
4. Sticker layer becomes visible on the overlay
|
||||
5. Next poll starts → mode auto-resets to `auto`
|
||||
6. Game Picker receives `stickers.mode: "auto"` → updates button
|
||||
|
||||
### Hide stickers during a busy poll
|
||||
|
||||
1. Poll is active, stickers are distracting
|
||||
2. Host clicks "Hide Stickers" → sends `stickers.setMode` with `mode: "hide"`
|
||||
3. Sticker layer hides on the overlay
|
||||
4. Poll ends, new poll starts → mode auto-resets to `auto`
|
||||
5. Stickers show normally with the new poll
|
||||
|
||||
### Sync on reconnect
|
||||
|
||||
1. Game Picker reconnects to vote-app
|
||||
2. Call `GET /api/stickers/visibility` to get current mode
|
||||
3. Update UI to match
|
||||
|
||||
## Notes
|
||||
|
||||
- Stickers accumulate in the DOM regardless of visibility — toggling just shows/hides the layer
|
||||
- The sticker layer is at z-index 1, behind the poll overlay (z-index 9997)
|
||||
- Sticker data persists across server restarts (stored in SQLite)
|
||||
- Stickers are cleared on `session.started` and `session.ended` events
|
||||
266
docs/superpowers/plans/2026-08-23-sticker-visibility-toggle.md
Normal file
266
docs/superpowers/plans/2026-08-23-sticker-visibility-toggle.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# Sticker Visibility Toggle Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a 3-way segmented toggle (Auto/Show/Hide) to the Picker screen that lets admins control sticker layer visibility on the downstream vote-app overlay via WebSocket.
|
||||
|
||||
**Architecture:** State (`stickerMode`) and UI live in the `Picker` component. `SessionInfo` (which owns the WebSocket connection) handles inbound `stickers.mode` events via a setter prop and exposes a send function to `Picker` via a ref. This follows the existing pattern used for poll state, leading game, etc.
|
||||
|
||||
**Tech Stack:** React 18, Tailwind CSS, WebSocket (existing `/api/sessions/live` connection)
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- All changes are in `frontend/src/pages/Picker.jsx` (contains both `Picker` and `SessionInfo` components)
|
||||
- Mode values are exactly `"auto"`, `"show"`, `"hide"` (matching the downstream API)
|
||||
- Default mode is `"auto"`
|
||||
- Follow existing code patterns — local `useState`, props passed to `SessionInfo`, Tailwind utility classes
|
||||
- No new dependencies
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Action | Responsibility |
|
||||
|------|--------|----------------|
|
||||
| `frontend/src/pages/Picker.jsx` | Modify | Add `stickerMode` state + `stickerSendRef` ref to `Picker`, render sticker toggle card in right column, add `setStickerMode` + `stickerSendRef` props to `SessionInfo`, handle `stickers.mode` WS events, populate send ref |
|
||||
|
||||
Single-file change. Both `Picker` and `SessionInfo` are defined in this file.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Wire sticker state and WebSocket plumbing
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/Picker.jsx:20-34` (Picker state declarations)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:1282-1297` (SessionInfo props)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:1304` (SessionInfo function signature)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:1385-1396` (WS auth_success handler, populate stickerSendRef)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:1470-1477` (WS message handler, add stickers.mode)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:1487-1491` (WS onclose, clear stickerSendRef)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:1498` (connectWs dependency array)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: nothing (first task)
|
||||
- Produces:
|
||||
- `stickerMode` state (`'auto' | 'show' | 'hide'`) in Picker
|
||||
- `stickerSendRef` ref (`.current` is `(mode: string) => void` when WS connected, `null` otherwise)
|
||||
- `setStickerMode` and `stickerSendRef` passed as props to SessionInfo
|
||||
|
||||
- [ ] **Step 1: Add stickerMode state and stickerSendRef to Picker**
|
||||
|
||||
In `frontend/src/pages/Picker.jsx`, after line 34 (`const [sessionEnded, setSessionEnded] = useState(false);`), add:
|
||||
|
||||
```jsx
|
||||
const [stickerMode, setStickerMode] = useState('auto');
|
||||
const stickerSendRef = useRef(null);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Pass new props to SessionInfo**
|
||||
|
||||
In `frontend/src/pages/Picker.jsx`, find the `<SessionInfo` JSX (around line 1282). Add two new props after `setGameSource={setGameSource}`:
|
||||
|
||||
```jsx
|
||||
<SessionInfo
|
||||
sessionId={activeSession.id}
|
||||
onGamesUpdate={gamesUpdateTrigger}
|
||||
playingGame={playingGame}
|
||||
setPlayingGame={setPlayingGame}
|
||||
setHasPlayedGames={setHasPlayedGames}
|
||||
setLeadingGame={setLeadingGame}
|
||||
setPollActive={setPollActive}
|
||||
pollActiveRef={pollActiveRef}
|
||||
setPollResult={setPollResult}
|
||||
setPollEndingAt={setPollEndingAt}
|
||||
setShowEndPollOptions={setShowEndPollOptions}
|
||||
pollStartedAtRef={pollStartedAtRef}
|
||||
setSelectedGame={setSelectedGame}
|
||||
setGameSource={setGameSource}
|
||||
setStickerMode={setStickerMode}
|
||||
stickerSendRef={stickerSendRef}
|
||||
/>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Accept new props in SessionInfo function signature**
|
||||
|
||||
In `frontend/src/pages/Picker.jsx`, update the `SessionInfo` function signature (line 1304) to destructure the two new props:
|
||||
|
||||
```jsx
|
||||
function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, setHasPlayedGames, setLeadingGame, setPollActive, pollActiveRef, setPollResult, setPollEndingAt, setShowEndPollOptions, pollStartedAtRef, setSelectedGame, setGameSource, setStickerMode, stickerSendRef }) {
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Populate stickerSendRef after WS authentication**
|
||||
|
||||
In the `connectWs` function, inside the `if (message.type === 'auth_success')` block (around line 1385), after the ping interval setup and before the `return;`, add the ref assignment:
|
||||
|
||||
```jsx
|
||||
if (message.type === 'auth_success') {
|
||||
console.log('[WebSocket] Authenticated, subscribing to session', sessionId);
|
||||
ws.send(JSON.stringify({ type: 'subscribe', sessionId: parseInt(sessionId) }));
|
||||
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'ping' }));
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
stickerSendRef.current = (mode) => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'stickers.setMode', data: { mode } }));
|
||||
}
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Handle inbound stickers.mode WS events**
|
||||
|
||||
In the `ws.onmessage` handler, after the `game.dismissed` block (around line 1468) and before the `reloadEvents.includes` check, add:
|
||||
|
||||
```jsx
|
||||
if (message.type === 'stickers.mode') {
|
||||
setStickerMode(message.mode);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Clear stickerSendRef on WS disconnect**
|
||||
|
||||
In the `ws.onclose` handler (around line 1487), add the ref cleanup alongside the existing cleanup:
|
||||
|
||||
```jsx
|
||||
ws.onclose = () => {
|
||||
console.log('[WebSocket] Disconnected, reconnecting in 3s...');
|
||||
clearInterval(pingIntervalRef.current);
|
||||
stickerSendRef.current = null;
|
||||
reconnectTimeoutRef.current = setTimeout(connectWs, 3000);
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Update connectWs dependency array**
|
||||
|
||||
Update the `useCallback` dependency array for `connectWs` (line 1498) to include the new dependencies:
|
||||
|
||||
```jsx
|
||||
}, [sessionId, token, loadGames, setPollActive, setPollResult, setPollEndingAt, setShowEndPollOptions, setLeadingGame, pollActiveRef, pollStartedAtRef, setSelectedGame, setGameSource, setStickerMode, stickerSendRef]);
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Verify no lint errors**
|
||||
|
||||
Run linter on `frontend/src/pages/Picker.jsx` and fix any issues introduced.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/Picker.jsx
|
||||
git commit -m "feat: wire sticker mode state and WebSocket plumbing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Render the sticker toggle UI with pulse animation
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/Picker.jsx:34-35` (add stickerPulse state, near other Picker state)
|
||||
- Modify: `frontend/src/pages/Picker.jsx:868-875` (insert toggle card above poll leader indicator in right column)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes:
|
||||
- `stickerMode` state (`'auto' | 'show' | 'hide'`) from Task 1
|
||||
- `stickerSendRef` ref from Task 1
|
||||
- `setStickerMode` setter from Task 1
|
||||
- Produces: Rendered sticker toggle card in Picker's right column
|
||||
|
||||
- [ ] **Step 1: Add stickerPulse state for auto-reset animation**
|
||||
|
||||
In `frontend/src/pages/Picker.jsx`, right after the `stickerSendRef` line added in Task 1 (after `const stickerSendRef = useRef(null);`), add:
|
||||
|
||||
```jsx
|
||||
const [stickerPulse, setStickerPulse] = useState(false);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add pulse trigger logic**
|
||||
|
||||
After the `stickerPulse` state declaration, add a `useEffect` that watches `stickerMode` for auto-reset events:
|
||||
|
||||
```jsx
|
||||
const prevStickerModeRef = useRef(stickerMode);
|
||||
useEffect(() => {
|
||||
if (stickerMode === 'auto' && prevStickerModeRef.current !== 'auto') {
|
||||
setStickerPulse(true);
|
||||
const timer = setTimeout(() => setStickerPulse(false), 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
prevStickerModeRef.current = stickerMode;
|
||||
}, [stickerMode]);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Render the sticker toggle card**
|
||||
|
||||
In the right column's `{/* Results Panel */}` div, after the error display and **before** the `{/* Poll Leader Indicator */}` comment (line 875), insert the sticker toggle card:
|
||||
|
||||
```jsx
|
||||
{/* Sticker Visibility Toggle */}
|
||||
<div
|
||||
className={`flex items-center justify-between border rounded-lg p-3 mb-4 transition-all duration-300 ${
|
||||
stickerPulse
|
||||
? 'border-indigo-400 dark:border-indigo-500 bg-indigo-50 dark:bg-indigo-900/20 animate-pulse'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">Stickers</span>
|
||||
<div className="flex border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden">
|
||||
{[
|
||||
{ value: 'auto', label: 'Auto', activeClass: 'bg-gray-500 text-white' },
|
||||
{ value: 'show', label: 'Show', activeClass: 'bg-green-500 text-white' },
|
||||
{ value: 'hide', label: 'Hide', activeClass: 'bg-red-500 text-white' },
|
||||
].map((opt, i) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => {
|
||||
setStickerMode(opt.value);
|
||||
stickerSendRef.current?.(opt.value);
|
||||
}}
|
||||
disabled={!stickerSendRef.current}
|
||||
className={`px-3 py-1.5 text-xs font-medium transition ${
|
||||
i === 1 ? 'border-x border-gray-300 dark:border-gray-600 ' : ''
|
||||
}${
|
||||
stickerMode === opt.value
|
||||
? opt.activeClass
|
||||
: 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300'
|
||||
}${
|
||||
!stickerSendRef.current ? ' opacity-50 cursor-not-allowed' : ''
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify no lint errors**
|
||||
|
||||
Run linter on `frontend/src/pages/Picker.jsx` and fix any issues introduced.
|
||||
|
||||
- [ ] **Step 5: Manual smoke test**
|
||||
|
||||
Start the dev server and verify:
|
||||
|
||||
1. The sticker toggle card appears above the poll leader / poll control cards in the right column
|
||||
2. All three segments (Auto/Show/Hide) are clickable and highlight correctly
|
||||
3. Auto shows gray, Show shows green, Hide shows red when active
|
||||
4. Inactive segments show the muted/white style
|
||||
5. The card is compact and single-row on desktop
|
||||
|
||||
Run: `cd frontend && npm run dev`
|
||||
|
||||
Open the Picker page in a browser while logged in with an active session. Click each segment and confirm visual feedback.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/Picker.jsx
|
||||
git commit -m "feat: add sticker visibility toggle UI with pulse animation"
|
||||
```
|
||||
@@ -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).
|
||||
@@ -2,7 +2,7 @@ export const branding = {
|
||||
app: {
|
||||
name: 'HSO Jackbox Game Picker',
|
||||
shortName: 'Jackbox Game Picker',
|
||||
version: '0.7.13 - Pokémon-Go-To-The-Polls Edition',
|
||||
version: '0.8.0 - Stickers That Stick Edition',
|
||||
description: 'Spicing up Hyper Spaceout game nights!',
|
||||
},
|
||||
meta: {
|
||||
|
||||
Reference in New Issue
Block a user