// QuickLog — a real action-logging modal. // // Opened from: // • the Insights screen's "Log something" / "Plan an action" buttons // • each Axis Detail page // • the dashboard CTA ribbon // // Behavior: // 1. Pull actions filtered by axis from /api/v1/master-equation/actions // 2. Let the user pick one; render a small dynamic form for the // action's `user_inputs` (signal IDs that require a numeric value) // 3. POST to /api/v1/master-equation/me/signal with the form values // 4. Trigger a vault refresh so every other component re-renders // with the new axis scores immediately. window.QuickLog = function QuickLog({ axisCode, onClose }) { const [actions, setActions] = React.useState(null); const [selectedId, setSelectedId] = React.useState(null); const [inputs, setInputs] = React.useState({}); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const [success, setSuccess] = React.useState(null); React.useEffect(() => { let dead = false; (async () => { try { const j = await window.CHVault.actions(axisCode || null); if (dead) return; setActions(j.actions || []); } catch (e) { if (!dead) { setError(e.message || 'Could not load actions'); setActions([]); } } })(); return () => { dead = true; }; }, [axisCode]); const selected = (actions || []).find(a => a.id === selectedId); async function submit(e) { e.preventDefault(); if (!selected) return; setBusy(true); setError(null); setSuccess(null); try { // Coerce inputs to numbers (the backend wants float). const numericInputs = {}; for (const sig of (selected.user_inputs || [])) { const v = inputs[sig]; if (v === undefined || v === '') throw new Error(`Enter a value for ${sig}`); const n = Number(v); if (!Number.isFinite(n)) throw new Error(`${sig} must be a number`); numericInputs[sig] = n; } const resp = await window.CHVault.logAction(selected.id, numericInputs); setSuccess(`Recorded · ${resp.persisted || 0} signal${(resp.persisted || 0) === 1 ? '' : 's'} written`); // Refresh global vault state so every component re-renders. await window.CHVault.refresh(); setTimeout(() => onClose && onClose(), 1100); } catch (e) { setError(e.message || 'Logging failed'); } setBusy(false); } const backdrop = { position: 'fixed', inset: 0, background: 'rgba(8,12,18,0.55)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999 }; const panel = { width: '100%', maxWidth: 520, maxHeight: '85vh', overflow: 'auto', background: 'var(--bg-parchment)', borderRadius: 16, border: '1px solid var(--border-1)', padding: 28, boxShadow: 'var(--elev-2)' }; const eyebrow = { fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--accent-clinical)' }; const cta = (disabled) => ({ flex: 1, padding: '12px 16px', borderRadius: 10, background: disabled ? 'var(--border-2)' : 'var(--accent-clinical)', color: 'white', border: 'none', fontWeight: 600, fontSize: 13.5, cursor: disabled ? 'wait' : 'pointer' }); const cancel = { flex: 1, padding: '12px 16px', borderRadius: 10, background: 'transparent', color: 'var(--fg-1)', border: '1px solid var(--border-1)', fontSize: 13.5, cursor: 'pointer' }; const input = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-bone)', border: '1px solid var(--border-1)', borderRadius: 8, padding: '10px 12px', color: 'var(--fg-1)', fontFamily: 'var(--font-sans)', fontSize: 14, outline: 'none' }; return (