# 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 ` ``` - [ ] **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 */}
Stickers
{[ { 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) => ( ))}
``` - [ ] **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" ```