/* ── ZONE 06 — Models: registry of every training run ──
Ported from the Hover design project. Every train() call persists a new model server-side (never
overwritten); push only moves which version the board pulls, so any past model (even one never
previously live) can be re-pushed -- e.g. reverting to a model that tested better in the field. */
function relTime(ts) {
const s = Math.max(0, Math.round((Date.now() - ts) / 1000));
if (s < 45) return 'just now';
const m = Math.round(s / 60);
if (m < 60) return m + ' min ago';
const h = Math.round(m / 60);
return h + ' h ago';
}
function ModelNote({ note, onSave }) {
const [editing, setEditing] = React.useState(false);
const [draft, setDraft] = React.useState(note || '');
const save = () => { onSave(draft.trim()); setEditing(false); };
if (editing) {
return (
setDraft(e.target.value)}
onBlur={save}
onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') { setDraft(note || ''); setEditing(false); } }}
style={{
width: '100%', background: 'var(--bg-0)', border: '1px solid var(--olive)',
borderRadius: 4, padding: '4px 8px', color: 'var(--t-1)',
fontSize: 'var(--text-sm)', outline: 'none',
boxShadow: '0 0 0 3px rgba(184,176,106,0.10)',
}}
/>
);
}
return note ? (
) : (
);
}
function ModelRow({ m, isLive, onPush, onNote }) {
const good = m.tone === 'good';
return (