// 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 */}
{/* 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]) => (
))
)}
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;