diff --git a/src/components/MissionTab.jsx b/src/components/MissionTab.jsx index 8b62296..a9152fc 100644 --- a/src/components/MissionTab.jsx +++ b/src/components/MissionTab.jsx @@ -177,9 +177,8 @@ export default function MissionTab({ authedPlayer }) { // ─── Data loading ─────────────────────────────────────────────── - const loadMissions = useCallback(async function loadMissions() { - setLoading(true); - setError(''); + const loadMissions = useCallback(async function loadMissions({ silent = false } = {}) { + if (!silent) { setLoading(true); setError(''); } try { if (isGM) { const [allRes, activeRes] = await Promise.all([ @@ -188,19 +187,18 @@ export default function MissionTab({ authedPlayer }) { ]); const all = Array.isArray(allRes.data) ? allRes.data.map(normalizeMission) : []; const active = normalizeMission(activeRes.data); - setMissions(all); - setActiveMission(active); - setSelectedMissionId(active?.id ? String(active.id) : (all[0]?.id ? String(all[0].id) : '')); + setMissions(prev => JSON.stringify(prev) !== JSON.stringify(all) ? all : prev); + setActiveMission(prev => JSON.stringify(prev) !== JSON.stringify(active) ? active : prev); + if (!silent) setSelectedMissionId(active?.id ? String(active.id) : (all[0]?.id ? String(all[0].id) : '')); } else { const activeRes = await axios.get('/api/missions/active/player'); - setMissions([]); - setActiveMission(normalizeMission(activeRes.data)); - setSelectedMissionId(''); + const active = normalizeMission(activeRes.data); + setActiveMission(prev => JSON.stringify(prev) !== JSON.stringify(active) ? active : prev); } } catch (e) { - setError(`Failed to load missions: ${e.response?.data?.error || e.message}`); + if (!silent) setError(`Failed to load missions: ${e.response?.data?.error || e.message}`); } finally { - setLoading(false); + if (!silent) setLoading(false); } }, [isGM]); @@ -208,7 +206,8 @@ export default function MissionTab({ authedPlayer }) { const loadRollFeed = useCallback(async function loadRollFeed() { try { const res = await axios.get('/api/missions/active/rolls/feed?limit=50'); - setRollFeed(Array.isArray(res.data) ? res.data : []); + const next = Array.isArray(res.data) ? res.data : []; + setRollFeed(prev => JSON.stringify(prev) !== JSON.stringify(next) ? next : prev); } catch (e) { // non-blocking } @@ -236,7 +235,7 @@ export default function MissionTab({ authedPlayer }) { useEffect(() => { if (isGM) return undefined; const timer = setInterval(() => { - loadMissions(); + loadMissions({ silent: true }); loadRollFeed(); }, 4000); return () => clearInterval(timer); @@ -291,6 +290,7 @@ export default function MissionTab({ authedPlayer }) { objectives: r.objectives ?? legacy, complications: r.complications ?? false, // never auto-revealed checks: r.checks || {}, + combat: r.combat ?? false, }; }, [currentScene]); @@ -407,7 +407,7 @@ export default function MissionTab({ authedPlayer }) { i === index ? { ...scene, ...updates } : scene ); await saveProgress(index, nextScenes); - await loadMissions(); + await loadMissions({ silent: true }); } async function updateCombatState(updates) { @@ -1208,10 +1208,10 @@ export default function MissionTab({ authedPlayer }) { {/* Combat Setup */}

Combat Setup

- {!isGM && !combatActive ? ( + {!isGM && !sceneReveal.combat ? (
⚔️
-

Combat briefing is classified.
Awaiting GM to initiate combat.

+

Combat briefing er klassificeret.
Afventer GM-ordre.

) : ( <> @@ -1255,7 +1255,7 @@ export default function MissionTab({ authedPlayer }) {
{/* ── Combat state panels (initiative, conditions, action ref, fear) ── */} - {combatActive && ( + {(combatActive || (!isGM && sceneReveal.combat)) && (
{/* Round counter + fear rating */} @@ -1786,6 +1786,14 @@ export default function MissionTab({ authedPlayer }) {
{sceneCombat.enemy || 'Custom/None'}
BS {sceneCombat.bs || 45} / WS {sceneCombat.ws || 45} · {sceneCombat.damage || '1d10+5'}
+ {/* Reveal toggle */} +