// The 5 weekly priorities — heart of the app. // Escalation system: rollovers field drives stage (fresh → rolled → final). // fresh = rollovers 0 — normal state // rolled = rollovers 1 — moved from last week, amber/red nudges // final = rollovers 2+ OR extended:true — locked, no more moves // WeekPressure banner shows urgency based on day of week. // ── Stage + week-phase helpers ───────────────────────────────────────────── function getPriorityStage(p) { if (p.extended || (p.rollovers || 0) >= 2) return 'final'; if ((p.rollovers || 0) === 1) return 'rolled'; return 'fresh'; } function getWeekPhase() { // 0=Sun 1=Mon 2=Tue 3=Wed 4=Thu 5=Fri 6=Sat const d = new Date().getDay(); if (d === 0) return 'closing'; if (d >= 5) return 'urgent'; if (d === 4) return 'warming'; return 'calm'; } function isWeekEnd() { const d = new Date().getDay(); return d === 5 || d === 6; // Fri or Sat } // ── WeekPressure banner ──────────────────────────────────────────────────── function WeekPressure({ priorities }) { const phase = getWeekPhase(); const DAY_LABELS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; // Map JS getDay() (Sun=0) → ribbon index (Mon=0 … Sun=6) const dayJs = new Date().getDay(); const todayIdx = dayJs === 0 ? 6 : dayJs - 1; const rolled = priorities.filter(p => getPriorityStage(p) === 'rolled').length; const finals = priorities.filter(p => getPriorityStage(p) === 'final').length; const atRisk = priorities.filter(p => getPriorityStage(p) !== 'final').length; const phaseLabel = { calm: '', warming: 'THU · WARMING UP', urgent: dayJs === 6 ? 'SAT · LAST CALL' : 'FRI · LAST CHANCE', closing: 'SUN · CLOSING TIME', }[phase]; const showAlert = phase !== 'calm' && atRisk > 0; return (
{showAlert && (
{phaseLabel} {atRisk} {atRisk === 1 ? 'priority' : 'priorities'} roll{atRisk === 1 ? 's' : ''} over if not closed this weekend. {rolled > 0 && ` ${rolled} already on borrowed time — finish ${rolled === 1 ? 'it' : 'them'}.`}
)}
{DAY_LABELS.map((d, i) => ( {d} ))} THE LINE ↑
); } // ── Main Priorities section ──────────────────────────────────────────────── function Priorities() { const CAP = 5; const { state, toggleTask, addTask, addPriority, renamePriority, transferPriority, deletePriority, completePriority, startBurst, startWrap, setPriorityCategory, grantExtension } = useStore(); const handleToggleTask = (priorityId, task) => { if (!task.done) { toggleTask(priorityId, task.id); startWrap(priorityId, task.id); } else { toggleTask(priorityId, task.id); } }; const usedCount = state.priorities.length; const pips = Array.from({ length: CAP }, (_, i) => i < usedCount); const covered = new Set(state.priorities.map((p) => p.tag)); return (

This week

Five in the running, max. Complete one to free up a slot — move one to next week, once.
{pips.map((on, i) => )} {usedCount}/{CAP}
{state.priorities.slice(0, CAP).map((p, idx) => ( handleToggleTask(p.id, task)} onAddTask={(label) => addTask(p.id, label)} onRename={(t) => renamePriority(p.id, t)} onSetCategory={(tag) => setPriorityCategory(p.id, tag)} onTransfer={() => transferPriority(p.id)} onExtend={() => grantExtension(p.id)} onDelete={() => deletePriority(p.id)} onComplete={() => completePriority(p.id)} onBurst={(tid) => startBurst(p.id, tid, state.burst.length)} /> ))} {Array.from({ length: Math.max(0, CAP - usedCount) }, (_, i) => ( ))}
); } // ── Coverage strip ───────────────────────────────────────────────────────── function CoverageStrip({ covered }) { const missing = CATEGORY_ORDER.filter((k) => !covered.has(k)); return (
Coverage
{CATEGORY_ORDER.map((k) => { const c = CATEGORIES[k]; const on = covered.has(k); return ( {c.label} ); })}
{missing.length > 0 && ( {missing.length === 5 ? 'Pick at least one of each for breadth' : missing.length === 1 ? `Missing: ${CATEGORIES[missing[0]].label}` : `${missing.length} categories uncovered`} )}
); } // ── Priority card ────────────────────────────────────────────────────────── function PriorityCard({ p, idx, onToggleTask, onAddTask, onRename, onSetCategory, onTransfer, onExtend, onDelete, onComplete, onBurst }) { const done = p.tasks.filter((t) => t.done).length; const pct = p.tasks.length ? Math.round(done / p.tasks.length * 100) : 0; const [adding, setAdding] = React.useState(false); const [draft, setDraft] = React.useState(''); const [catOpen, setCatOpen] = React.useState(false); const [extConfirm, setExtConfirm] = React.useState(false); const titleRef = React.useRef(null); const stage = getPriorityStage(p); const weekend = isWeekEnd(); const canTransfer = stage === 'fresh' && !p.transferred; const lastChance = stage === 'fresh' && weekend; const commitTitle = () => { const v = titleRef.current?.textContent?.trim(); if (v && v !== p.title) onRename(v); }; const submitTask = (e) => { e?.preventDefault(); if (draft.trim()) onAddTask(draft.trim()); setDraft(''); setAdding(false); }; const cat = CATEGORIES[p.tag] || CATEGORIES.ops; // Stage-specific tag label const stageTag = (() => { if (stage === 'final') return { label: 'final week — no more moves', cls: 'crit' }; if (stage === 'rolled') return { label: `rolled over · week ${(p.rollovers || 1) + 1}`, cls: 'crit' }; if (stage === 'fresh' && weekend) return { label: 'last chance — closes tomorrow', cls: 'last' }; return null; })(); return (
{/* ── Top urgency banners ── always visible, can't be missed ── */} {lastChance && (
Last chance this week.{' '} {canTransfer ? 'Close it this weekend or move it to next week — one free move left.' : 'Transfer already used — you must close this or grant an extension.'}
{canTransfer && ( )}
)} {stage === 'rolled' && (
🔴 Carried over from last week.{' '} This must close this week — or use a special one-week extension.
{!p.extended && !extConfirm && ( )} {!p.extended && extConfirm && ( This is the final extension.{' '} {' · '} )}
)} {stage === 'final' && (
🔒 {p.extended ? 'Special extension week.' : 'Final week.'}{' '} No more moves. Close it or cut it — this is the end of the line.
)}
{idx}

{ if (e.key === 'Enter') { e.preventDefault(); e.currentTarget.blur(); } }}> {p.title}

{stageTag && ( • {stageTag.label} )} {catOpen && (
setCatOpen(false)}>
Category
{CATEGORY_ORDER.map((k) => { const c = CATEGORIES[k]; return ( ); })}
)}
{p.tasks.map((t) => (
onToggleTask(t)}> {t.done && } {t.label} { e.stopPropagation(); onBurst(t.id); }}> ⚡ burst
))} {adding ? (
setDraft(e.target.value)} onBlur={submitTask} onKeyDown={(e) => { if (e.key === 'Escape') { setAdding(false); setDraft(''); } }} placeholder="Next concrete step…" style={{ flex: 1, fontFamily: 'inherit', fontSize: 13, padding: '6px 10px', border: '1px solid var(--indigo-300)', borderRadius: 8, background: 'var(--bg-elev)', color: 'var(--fg)', outline: 'none', boxShadow: 'var(--focus-ring)', }} />
) : ( )}
{done}/{p.tasks.length} {stage === 'final' ? '🔒 no more moves' : stage === 'rolled' ? '⚠ week 2 — extension available' : lastChance ? '⚡ closes this weekend' : canTransfer ? 'transfer available' : 'transfer used'}
); } function EmptySlot({ onAdd, index }) { const [adding, setAdding] = React.useState(false); const [draft, setDraft] = React.useState(''); if (adding) { return (
{ e.preventDefault(); if (draft.trim()) onAdd(draft.trim()); setDraft(''); setAdding(false); }} style={{ display: 'block', textAlign: 'left', cursor: 'auto' }}>
Priority #{index}
setDraft(e.target.value)} onBlur={() => { if (!draft.trim()) setAdding(false); else { onAdd(draft.trim()); setDraft(''); setAdding(false); } }} onKeyDown={(e) => { if (e.key === 'Escape') { setAdding(false); setDraft(''); } }} placeholder="One big outcome this week…" style={{ width: '100%', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 16, padding: '6px 8px', border: '1px solid var(--indigo-300)', borderRadius: 8, background: 'var(--bg-elev)', color: 'var(--fg)', outline: 'none', boxShadow: 'var(--focus-ring)', }} />
); } return ( ); } Object.assign(window, { Priorities });