const view = document.getElementById('view'); let countdownTimer = null; let renderedState = null; // which runtime_state the DOM currently shows let dismissedNudge = null; // text of the nudge the user has dismissed function post(path, body) { return fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined, }); } function fmt(secs) { secs = Math.max(0, Math.floor(secs)); const m = String(Math.floor(secs / 60)).padStart(2, '0'); const s = String(secs % 60).padStart(2, '0'); return `${m}:${s}`; } function evidenceBlock(ev) { if (!ev) return ''; const health = ev.available ? `tracking` : `evidence unavailable: ${ev.reason || 'unknown'}`; const now = ev.current && (ev.current.class || ev.current.title) ? `${ev.current.class || '?'} · ${ev.current.title || ''}` : '—'; const rows = (ev.buckets || []).map(b => `
  • ${(b.class || '?')} · ${b.title || ''}${fmt(b.seconds)}
  • `).join(''); return `
    Now: ${now}   ${health}
    Context switches: ${ev.switch_count || 0}
    `; } function updateActiveDrift(drift) { const el = document.getElementById('drift'); if (!el) return; const status = drift && drift.status; if (status === 'drifting') { el.innerHTML = `
    ⚠ Possible drift
    ${drift.reason || ''}
    `; document.getElementById('refocus').onclick = () => post('/refocus'); document.getElementById('ontask').onclick = () => post('/ontask'); document.getElementById('enddrift').onclick = () => post('/complete'); return; } const nudge = (drift && drift.nudge) || ''; if (!nudge) dismissedNudge = null; // trajectory recovered; allow future nudges if (nudge && nudge !== dismissedNudge) { el.innerHTML = `
    Heads up
    ${nudge}
    `; document.getElementById('dismissnudge').onclick = () => { dismissedNudge = nudge; const e = document.getElementById('drift'); if (e) e.innerHTML = ''; }; return; } if (status === 'pending') { el.innerHTML = `
    checking focus…
    `; return; } el.innerHTML = ''; } function updatePlanningCoach(coach) { const statusEl = document.getElementById('coachStatus'); if (!statusEl) return; if (!coach || coach.status === 'idle') { statusEl.textContent = ''; return; } if (coach.status === 'pending') { statusEl.textContent = 'Sharpening…'; return; } if (coach.status === 'error') { statusEl.textContent = coach.error || 'coach unavailable'; return; } if (coach.status === 'ready' && coach.proposal) { statusEl.textContent = 'AI suggestion applied — edit freely.'; // Pre-fill once per ready proposal so later manual edits are not clobbered // by subsequent SSE ticks (idempotent via the dataset stamp below). const stamp = JSON.stringify(coach.proposal); if (statusEl.dataset.applied === stamp) return; statusEl.dataset.applied = stamp; const na = document.getElementById('na'), sc = document.getElementById('sc'), mins = document.getElementById('mins'), start = document.getElementById('start'); if (na && !na.value.trim()) na.value = coach.proposal.next_action || ''; if (sc && !sc.value.trim()) sc.value = coach.proposal.success_condition || ''; if (mins && coach.proposal.timebox_secs) mins.value = Math.round(coach.proposal.timebox_secs / 60); if (start) start.disabled = !(na.value.trim() && sc.value.trim() && +mins.value > 0); const apps = document.getElementById('apps'); if (apps && !apps.value.trim() && Array.isArray(coach.proposal.allowed_window_classes)) { apps.value = coach.proposal.allowed_window_classes.join(', '); } } } function render(state) { const rs = state.runtime_state; if (rs === 'planning' && renderedState === 'planning') { updatePlanningCoach(state.coach); // partial update only return; } if (rs === 'active' && renderedState === 'active') { updateActiveDrift(state.drift); return; } if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; } renderedState = rs; if (rs === 'locked') { view.innerHTML = `Locked

    No active commitment.

    `; document.getElementById('plan').onclick = () => post('/planning'); } else if (rs === 'planning') { view.innerHTML = `Planning
    `; const na = document.getElementById('na'), sc = document.getElementById('sc'), mins = document.getElementById('mins'), start = document.getElementById('start'); const check = () => { start.disabled = !(na.value.trim() && sc.value.trim() && +mins.value > 0); }; [na, sc, mins].forEach(el => el.oninput = check); start.onclick = () => post('/commitment', { next_action: na.value.trim(), success_condition: sc.value.trim(), timebox_secs: Math.round(+mins.value * 60), allowed_window_classes: (document.getElementById('apps').value || '') .split(',').map(s => s.trim()).filter(Boolean), }); document.getElementById('sharpen').onclick = () => { const intent = document.getElementById('intent').value.trim(); if (intent) post('/coach', { intent }); }; updatePlanningCoach(state.coach); } else if (rs === 'active') { const c = state.commitment || {}; view.innerHTML = `Active
    --:--
    ${c.next_action || ''}

    Done when: ${c.success_condition || ''}

    ${evidenceBlock(state.evidence)} `; document.getElementById('done').onclick = () => post('/complete'); const t = document.getElementById('t'); const tick = () => { t.textContent = fmt((c.deadline_unix_secs || 0) - Date.now() / 1000); }; tick(); countdownTimer = setInterval(tick, 250); updateActiveDrift(state.drift); } else if (rs === 'review') { const c = state.commitment || {}; view.innerHTML = `Review

    Session ended

    ${c.next_action || ''}

    ${evidenceBlock(state.evidence)} `; document.getElementById('end').onclick = () => post('/end'); } else { view.textContent = rs; } } const es = new EventSource('/events'); es.onmessage = (e) => render(JSON.parse(e.data)); es.onerror = () => { view.textContent = 'disconnected — is antidriftd running?'; };