// The three islands: ListsIsland, TasksIsland, DetailsIsland
const { useState, useEffect, useRef, useMemo } = React;
// ---------- Helpers ----------
const fmtDate = (iso) => {
if (!iso) return null;
const d = new Date(iso);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const target = new Date(d.getFullYear(), d.getMonth(), d.getDate());
const diff = Math.round((target - today) / 86400000);
if (diff === 0) return 'Today';
if (diff === 1) return 'Tomorrow';
if (diff === -1) return 'Yesterday';
if (diff < 0) return `${Math.abs(diff)}d overdue`;
if (diff < 7) return d.toLocaleDateString(undefined, { weekday: 'short' });
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
};
const isToday = (iso) => {
if (!iso) return false;
const d = new Date(iso); const n = new Date();
return d.getFullYear() === n.getFullYear() && d.getMonth() === n.getMonth() && d.getDate() === n.getDate();
};
const isOverdue = (iso) => {
if (!iso) return false;
const d = new Date(iso); const n = new Date();
return d < new Date(n.getFullYear(), n.getMonth(), n.getDate());
};
const STATUS_LABEL = {
idle: 'Idle', queued: 'Queued', running: 'Running',
review: 'Review', done: 'Done', error: 'Error',
};
const relTime = (iso) => {
if (!iso) return '';
const diff = Math.max(0, Date.now() - new Date(iso).getTime());
const s = Math.floor(diff / 1000);
if (s < 60) return s + 's ago';
const m = Math.floor(s / 60);
if (m < 60) return m + 'm ago';
const h = Math.floor(m / 60);
if (h < 24) return h + 'h ago';
return Math.floor(h / 24) + 'd ago';
};
const logTime = (iso) => {
const d = new Date(iso);
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
};
// ---------- Checkbox ----------
const Checkbox = ({ done, onToggle, size }) => (
{ e.stopPropagation(); onToggle(); }}
role="checkbox"
aria-checked={done}
>
);
// ---------- Lists Island ----------
const ListsIsland = ({ activeList, setActiveList, counts, search, setSearch }) => {
return (
setSearch(e.target.value)}
/>
⌘K
Smart lists
{SEED_LISTS.map((l) => (
setActiveList(l.id)}
>
{l.name}
{counts[l.id] ?? ''}
))}
My lists
{SEED_USER_LISTS.map((l) => (
setActiveList(l.id)}
>
{l.name}
{counts[l.id] ?? ''}
))}
AK
Aoife Kelly
rider.island / local
);
};
// ---------- Tasks Island ----------
const TaskRow = ({ task, selected, onSelect, onToggle, onStar, leaving, entering }) => {
const [starPulse, setStarPulse] = useState(false);
const handleStar = (e) => {
e.stopPropagation();
setStarPulse(true);
setTimeout(() => setStarPulse(false), 400);
onStar();
};
const list = SEED_USER_LISTS.find((l) => l.id === task.list);
const overdue = isOverdue(task.due) && !task.done;
const today = isToday(task.due);
return (
{task.title}
{task.agent && (
{STATUS_LABEL[task.agent.status]}
)}
{list && (
{list.name}
)}
{task.agent?.branch && (
{task.agent.branch.replace('agent/', '')}
)}
{task.agent?.diff && task.agent.diff.files > 0 && (
+{task.agent.diff.additions}
−{task.agent.diff.deletions}
)}
{task.due && !task.agent && (
{fmtDate(task.due)}
)}
{task.subtasks && task.subtasks.length > 0 && (
{task.subtasks.filter((s) => s.done).length}/{task.subtasks.length} steps
)}
{task.tags && task.tags.map((t) => {t})}
{task.agent && task.agent.status === 'running' && task.agent.log && task.agent.log.length > 0 && (() => {
const last = task.agent.log[task.agent.log.length - 1];
return (
›
{last.m}
);
})()}
);
};
const TasksIsland = ({
tasks, selectedId, setSelected,
onToggle, onStar, onAdd,
leavingIds, enteringIds,
activeList, showCompleted, setShowCompleted,
}) => {
const [newTitle, setNewTitle] = useState('');
const inputRef = useRef(null);
const now = new Date();
const dateLine = now.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' });
const activeTasks = tasks.filter((t) => !t.done);
const doneTasks = tasks.filter((t) => t.done);
const overdueTasks = activeTasks.filter((t) => isOverdue(t.due));
const todayTasks = activeTasks.filter((t) => !isOverdue(t.due));
const listMeta = SEED_LISTS.find((l) => l.id === activeList) || SEED_USER_LISTS.find((l) => l.id === activeList);
const title = activeList === 'myday' ? 'My Day' : (listMeta?.name || 'Tasks');
const eyebrow = activeList === 'myday' ? dateLine : `${activeTasks.length} open · ${doneTasks.length} done`;
const handleSubmit = (e) => {
e.preventDefault();
if (newTitle.trim()) {
onAdd(newTitle.trim());
setNewTitle('');
}
};
return (
{activeList === 'myday' ? 'My Day' : 'List'}
{title}
{activeList === 'myday' ? dateLine : eyebrow}
·
{activeTasks.length} open
{overdueTasks.length > 0 && (
<>
Overdue
{overdueTasks.map((t) => (
setSelected(t.id)}
onToggle={() => onToggle(t.id)}
onStar={() => onStar(t.id)}
leaving={leavingIds.includes(t.id)}
entering={enteringIds.includes(t.id)}
/>
))}
>
)}
{todayTasks.length > 0 && (
<>
{overdueTasks.length > 0 && Tasks
}
{todayTasks.map((t) => (
setSelected(t.id)}
onToggle={() => onToggle(t.id)}
onStar={() => onStar(t.id)}
leaving={leavingIds.includes(t.id)}
entering={enteringIds.includes(t.id)}
/>
))}
>
)}
{activeTasks.length === 0 && (
All clear
The harbor is calm. Add a task above.
)}
{showCompleted && doneTasks.length > 0 && (
<>
Completed · {doneTasks.length}
{doneTasks.map((t) => (
setSelected(t.id)}
onToggle={() => onToggle(t.id)}
onStar={() => onStar(t.id)}
leaving={leavingIds.includes(t.id)}
entering={enteringIds.includes(t.id)}
/>
))}
>
)}
);
};
// ---------- Worktree + Terminal sub-components ----------
const WorktreeCard = ({ agent, onOpenDiff, onOpenWorktree }) => {
if (!agent) return null;
return (
Worktree
{agent.worktree}
Branch
{agent.branch}
← {agent.baseBranch}
Diff
{agent.diff.files > 0 ? (
{agent.diff.files} files
+{agent.diff.additions}
−{agent.diff.deletions}
{Array.from({ length: 5 }).map((_, i) => {
const total = agent.diff.additions + agent.diff.deletions || 1;
const addShare = Math.round((agent.diff.additions / total) * 5);
return ;
})}
) : No changes yet}
{agent.commits > 0 && (
Commits
{agent.commits} on branch
)}
);
};
const SessionTerminal = ({ agent, onInput }) => {
const bodyRef = useRef(null);
const [draft, setDraft] = useState('');
useEffect(() => {
if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
}, [agent?.log?.length]);
if (!agent) return null;
const running = agent.status === 'running';
const statusLabel = running ? 'LIVE' : STATUS_LABEL[agent.status];
return (
claude-session · {agent.branch}
{running
?
LIVE
:
{statusLabel}}
{(agent.log || []).map((l, i) => (
{logTime(l.t)}
{l.k === 'msg' ? 'claude' : l.k === 'tool' ? 'tool' : l.k === 'sys' ? 'sys' : l.k === 'stdout' ? 'out' : l.k === 'stderr' ? 'err' : l.k}
{l.m}
))}
{running && (
{logTime(new Date().toISOString())}
claude
)}
›
setDraft(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter' && draft.trim()) { onInput(draft); setDraft(''); } }}
disabled={!running}
style={{ flex: 1, fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--text)' }}
/>
);
};
// ---------- Details Island ----------
const DetailsIsland = ({ task, onUpdate, onDelete, onToggle, onStar, onAgentAction, onOpenDiff, onOpenWorktree, onAgentInput }) => {
if (!task) {
return (
No task selected
Pick a task from the middle
to see its details here.
);
}
const list = SEED_USER_LISTS.find((l) => l.id === task.list);
const created = task.created ? new Date(task.created).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : '—';
const due = task.due ? new Date(task.due).toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }) : 'None';
const toggleSub = (sid) => {
const next = task.subtasks.map((s) => s.id === sid ? { ...s, done: !s.done } : s);
onUpdate({ ...task, subtasks: next });
};
const addSub = (title) => {
if (!title.trim()) return;
const next = [...(task.subtasks || []), { id: 's' + Date.now(), title: title.trim(), done: false }];
onUpdate({ ...task, subtasks: next });
};
const [subDraft, setSubDraft] = useState('');
return (
Logbook
#{task.id}
{task.agent ? 'Agent task' : 'Task details'}
{task.agent && (
{STATUS_LABEL[task.agent.status]}
{task.agent.model}
·
{task.agent.turns} turns
·
{(task.agent.tokens / 1000).toFixed(1)}k tok
{task.agent.startedAt && <>·{relTime(task.agent.startedAt)}>}
{task.agent.status === 'running' ? (
) : task.agent.status === 'idle' || task.agent.status === 'error' || task.agent.status === 'queued' ? (
) : null}
)}
onToggle(task.id)} />
{task.agent && (
Worktree
onOpenDiff(task.id)}
onOpenWorktree={() => onOpenWorktree(task.id)}
/>
)}
{task.agent && (
Session output
{(task.agent.log || []).length} lines
onAgentInput(task.id, msg)}
/>
)}
{(task.subtasks || []).length > 0 && (
Steps · {task.subtasks.filter(s => s.done).length}/{task.subtasks.length}
{task.subtasks.map((s) => (
toggleSub(s.id)} />
{s.title}
))}
)}
List
{list ? list.name : '—'}
Due
{due}
{!task.agent && (
Reminder
{task.reminder || 'None'}
)}
Important
{task.starred ? 'Starred' : 'No'}
{(task.tags || []).length > 0 && (
Tags
{task.tags.map((t) => {t})}
+ add
)}
Created {created}
);
};
window.ListsIsland = ListsIsland;
window.TasksIsland = TasksIsland;
window.DetailsIsland = DetailsIsland;