/* ================================================================== Engine Config modal — curated subset (timer / navigation / scoring / atomic_block_shuffle). Loads current config from the test detail endpoint, PATCHes back to /api/tests/:display_id/engine-config. ================================================================== */ const EngineConfigModal = ({ test, onClose, onSaved }) => { const [cfg, setCfg] = React.useState(null); const [loading, setLoading] = React.useState(false); const [saving, setSaving] = React.useState(false); const [err, setErr] = React.useState(""); React.useEffect(() => { if (!test) { setCfg(null); setErr(""); return; } let cancelled = false; setLoading(true); window.AppApi.get(`/tests/${test.id}`) .then(d => { if (!cancelled) setCfg(d.engine_config); }) .catch(e => { if (!cancelled) setErr(e.message || "failed to load config"); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [test]); if (!test) return null; const set = (path, value) => { setCfg(prev => { const next = JSON.parse(JSON.stringify(prev)); const keys = path.split("."); let node = next; for (let i = 0; i < keys.length - 1; i++) node = node[keys[i]]; node[keys[keys.length - 1]] = value; return next; }); }; const handleSave = async () => { setSaving(true); setErr(""); try { const r = await window.AppApi.patch(`/tests/${test.id}/engine-config`, cfg); if (onSaved) onSaved(r.engine_config); onClose(); } catch (e) { setErr(e.message || "failed to save"); } finally { setSaving(false); } }; const SectionTitle = ({ children }) => (