// Inbox — approvals queue (checkpoint pattern). Agent actions awaiting human OK. // Wired to POST /api/approvals/{id}/decide for real persistence. function InboxScreen({ go }) { const d = LifeData; const [decisions, setDecisions] = React.useState({}); const handle = async (id, action) => { setDecisions(s => ({ ...s, [id]: action })); const approval = d.approvals.find(a => a.id === id); try { await fetch(`/api/approvals/${id}/decide`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, kind: approval?.kind || 'bill' }), }); } catch (err) { console.error('Approval decision failed:', err); } }; const grouped = { now: d.approvals.filter(a => a.urgency === 'now'), today: d.approvals.filter(a => a.urgency === 'today'), soon: d.approvals.filter(a => a.urgency === 'soon'), later: d.approvals.filter(a => a.urgency === 'later'), }; const headings = { now: 'Now · go to laptop', today: 'Today', soon: 'This week', later: 'Later', }; const pending = d.approvals.filter(a => !decisions[a.id]).length; return (
{/* Header */}
Approvals · Checkpoint
{pending === 0 ? 'All caught up.' : `${pending} need a yes.`}
Life OS won't take irreversible actions without you. Approve once and it'll handle the rest.
{/* Spend pulse */}
API spend · this month
Ollama
free
{/* Groups */} {d.approvals.length === 0 ? (
Nothing pending
Life OS will surface actions here when bills are due, groceries are low, or it needs your OK.
) : ( Object.entries(grouped).filter(([, arr]) => arr.length > 0).map(([key, arr]) => (
{arr.map(a => ( ))}
)) )}
Payments, deletions and submissions always pause here.
Browser sessions live on your Thinkpad only.
); } function ApprovalCard({ a, decision, onAction }) { const iconMap = { bill: , grocery: , reply: , cart: , memory: , }; const kindLabel = { bill: 'BILL AUTOPILOT', grocery: 'GROCERY ORACLE', reply: 'REPLY DRAFT', cart: 'CART READY', memory: 'NEW MEMORY', }; const handled = decision === 'approve' || decision === 'reject'; return (
{iconMap[a.kind] || }
{kindLabel[a.kind] || 'ACTION'}
{a.title}
{a.subtitle}
{a.detail && (
{a.detail}
)} {/* actions */}
{a.kind === 'cart' ? ( ) : ( )}
); } function SpendBar({ label, used, budget, color }) { const pct = budget > 0 ? Math.min(used / budget, 1) : 0; const overBudget = used >= budget * 0.9; return (
{label}
${used.toFixed(2)} / ${budget.toFixed(2)}
); } window.InboxScreen = InboxScreen;