/* global React */
/* =============================================================================
   ACTIONS DASHBOARD v2  —  backed by the `actions` table
   3-column board + DetailDrawer + TriageDrawer + Archive Done
   ========================================================================== */

const { useState, useRef, useEffect, useCallback } = React;

const TASK_DOMAINS = {
  creative:      { label: "Creative",       ico: "fire-1",     color: "var(--axis-creative)"      },
  physical:      { label: "Physical",       ico: "cross",      color: "var(--axis-physical)"      },
  inner:         { label: "Inner World",    ico: "hold-eye",   color: "var(--axis-inner)"         },
  interpersonal: { label: "Interpersonal",  ico: "book",       color: "var(--axis-interpersonal)" },
  adventure:     { label: "Adventure",      ico: "hold-globe", color: "var(--axis-adventure)"     },
  admin:         { label: "Life Admin",     ico: "settings",   color: "var(--axis-life-admin)"    },
  aligned:       { label: "True North",     ico: "star-maps", color: "var(--gold-1)"           },
};

/* ── Anchored Ledger — Value Velocity panel with nested practices ─────────────
   Frozen creation order (never sorted by velocity). Caret expands practices in
   place (multi-open). Row/bar click opens the ValueDrawer (8-week trend + evidence).
   Uncharted (no data yet) renders distinct from quiet (measured 0). */
const LEDGER_WD = ["S", "M", "T", "W", "T", "F", "S"];
const ledgerWd = (iso) => LEDGER_WD[new Date(iso + "T00:00:00").getDay()];
const LEDGER_DAY_FULL  = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const LEDGER_MONTH_SHT = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const ledgerDayLabel = (iso, isToday) => {
  if (isToday) return 'Today';
  const d = new Date(iso + 'T00:00:00');
  return LEDGER_DAY_FULL[d.getDay()] + ', ' + LEDGER_MONTH_SHT[d.getMonth()] + ' ' + d.getDate();
};

function LedgerFlat({ rows, unanchored, win, onLogDay }) {
  const rowsRef = useRef(null);
  const [colPos, setColPos] = useState(null);
  useEffect(() => {
    if (!rowsRef.current) return;
    const cell = rowsRef.current.querySelector('.ledger-cell.is-today');
    if (!cell) return;
    const rr = rowsRef.current.getBoundingClientRect();
    const cr = cell.getBoundingClientRect();
    setColPos({ left: cr.left - rr.left, width: cr.width });
  }, [win.today_index, win.days.length]);
  const flat = [];
  rows.forEach((r) => r.practices.forEach((p) => flat.push({ ...p, value: r.name, domain: p.domain || r.domain })));
  (unanchored || []).forEach((p) => flat.push({ ...p, value: '— unanchored' }));
  return (
    <div className="ledger-flat">
      <div className="ledger-flat__head">
        <span className="ledger-flat__lead">Practice · Value</span>
        <span className="ledger-track">
          {win.days.map((d, di) => (
            <span key={di} className={"ledger-day" + (di === win.today_index ? " is-today" : "")} title={ledgerDayLabel(d, di === win.today_index)}>{ledgerWd(d)}
              <span className="ledger-day__num">{new Date(d + 'T00:00:00').getDate()}</span>
            </span>
          ))}
        </span>
      </div>
      <div className="ledger-flat__rows" ref={rowsRef}>
        {colPos && <div className="ledger-col-today" aria-hidden="true" style={{ left: colPos.left + 'px', width: colPos.width + 'px' }} />}
      {flat.length === 0 && <div className="ledger-empty">No practices logged.</div>}
      {flat.map((p) => {
        const pc = (TASK_DOMAINS[p.domain] || {}).color || 'var(--gold-1)';
        return (
          <div key={p.practice_id} className="ledger-frow">
            <span className="ledger-frow__id">
              <span className="ledger-fdot" style={{ background: pc }} />
              <span className="ledger-fname">{p.name}</span>
              <span className="ledger-fval">{p.value}</span>
            </span>
            <span className="ledger-track">
              {win.days.map((_, di) => {
                const on = p.nodes.includes(di);
                return (
                  <button key={di} type="button" className={"ledger-cell" + (di === win.today_index ? " is-today" : "")} onClick={() => onLogDay(p.practice_id, win.days[di])} title={(on ? 'Logged ' : 'Log ') + win.days[di]} aria-label={(on ? 'Logged ' : 'Log ') + p.name + ' ' + win.days[di]}><i className={on ? 'on' : ''} style={on ? { background: pc, boxShadow: `0 0 6px ${pc}aa` } : undefined} /></button>
                );
              })}
            </span>
          </div>
        );
      })}
      </div>
    </div>
  );
}

function PracticeGrid({ r, win, meta, color }) {
  const gridRef = useRef(null);
  const [colPos, setColPos] = useState(null);
  useEffect(() => {
    if (!gridRef.current) return;
    const cell = gridRef.current.querySelector('.ledger-cell.is-today');
    if (!cell) return;
    const rr = gridRef.current.getBoundingClientRect();
    const cr = cell.getBoundingClientRect();
    setColPos({ left: cr.left - rr.left, width: cr.width });
  }, [win.today_index, win.days.length]);
  return (
    <div className="ledger-practices" id={'prac-' + r.value_id} ref={gridRef} style={{ position: 'relative' }}>
      {colPos && <div className="ledger-col-today" aria-hidden="true" style={{ left: colPos.left + 'px', width: colPos.width + 'px' }} />}
      <div className="ledger-axis">
        <span className="ledger-pname" />
        <span className="ledger-track">
          {win.days.map((d, di) => (
            <span key={di} className={"ledger-day" + (di === win.today_index ? " is-today" : "")} title={ledgerDayLabel(d, di === win.today_index)}>{ledgerWd(d)}
              <span className="ledger-day__num">{new Date(d + 'T00:00:00').getDate()}</span>
            </span>
          ))}
        </span>
      </div>
      {r.practices.length === 0 ? (
        <div className="ledger-empty">No logged vectors in this window.</div>
      ) : r.practices.map((p) => {
        const pc = (TASK_DOMAINS[p.domain] || meta).color || color;
        return (
          <div key={p.practice_id} className="ledger-prow">
            <span className="ledger-pname">{p.name}</span>
            <span className="ledger-track">
              {win.days.map((_, di) => {
                const on = p.nodes.includes(di);
                return (
                  <span key={di} className={"ledger-cell" + (di === win.today_index ? " is-today" : "")}>
                    <i className={on ? 'on' : ''} style={on ? { background: pc, boxShadow: '0 0 6px ' + pc + 'aa' } : undefined} />
                  </span>
                );
              })}
            </span>
          </div>
        );
      })}
    </div>
  );
}

function ValueLedgerPanel({ ledger, expanded, onToggle, onDetail, velocityWindow, totalEvents, domainFilter, onLogDay }) {
  const allRows = ledger.rows || [];
  const rows = (domainFilter && domainFilter.length) ? allRows.filter((r) => domainFilter.includes(r.domain)) : allRows;
  const win = ledger.window || { days: [], today_index: 6 };
  const maxCount = Math.max(1, ...rows.map((r) => r.count || 0));
  const windowLabel = velocityWindow === '30d' ? '30 days' : '7 days';
  const [mode, setMode] = React.useState(() =>
    (typeof location !== 'undefined' && location.hash.includes('flat')) ? 'allPractices' : 'byValue');
  return (
    <section className="dash2-capstrip__panel dash2-meter-panel">
      <div className="dash2-capstrip__head">
        <span className="dash2-capstrip__title">Momentum Log</span>
        {mode === 'byValue' && (
          <span className="ledger-key" aria-hidden="true">
            <span className="ledger-key__solid" /> {windowLabel}
            <span className="ledger-key__hatch" /> today
          </span>
        )}
      </div>
      <div className="ledger-tabs">
        <button className={"ledger-tab" + (mode === 'byValue' ? ' is-active' : '')}
          onClick={() => setMode('byValue')} aria-pressed={mode === 'byValue'}>By Value</button>
        <button className={"ledger-tab" + (mode === 'allPractices' ? ' is-active' : '')}
          onClick={() => setMode('allPractices')} aria-pressed={mode === 'allPractices'}>All Practices</button>
        <span className="ledger-tabs__sp" />
      </div>
      <div className="ledger-body">
      {mode === 'byValue' ? (
      <div className="dash2-meter-list">
        {rows.map((r) => {
          const meta = TASK_DOMAINS[r.domain] || {};
          const color = meta.color || 'var(--gold-1)';
          const open = expanded.has(r.value_id);
          const item = { valueGoalId: r.value_id, label: r.name, domain: r.domain,
            count: r.count, todayCount: r.today_count, trend: r.trend,
            percent: r.percent, primaryDomain: r.domain };
          return (
            <div key={r.value_id} className="ledger-value">
              <div className="ledger-row2">
                <button className="ledger-arrow" onClick={() => onToggle(r.value_id)}
                  aria-expanded={open} aria-controls={`prac-${r.value_id}`}
                  aria-label={open ? `Collapse ${r.name}` : `Expand ${r.name}, ${r.practice_count} practices`}>
                  {open ? '▾' : '▸'}
                </button>
                <button className="ledger-name" onClick={() => onToggle(r.value_id)}
                  title={`${r.practice_count} practices — click to expand`}>
                  {meta.ico && (
                    <span className="ledger-name__glyph" aria-hidden="true"
                      style={{ "--ico": `url(/dashboard-ui/assets/icons/${meta.ico}.svg)`, background: color }} />
                  )}
                  <span className="ledger-name__text" title={r.name}>{r.name}</span>
                  <span className="ledger-name__n">{r.practice_count}</span>
                </button>
                {r.state === 'uncharted' ? (
                  <span className="ledger-vbar--empty">— uncharted</span>
                ) : (
                  <button className="ledger-vbar" onClick={() => onDetail(item)}
                    title={`${r.count} this ${velocityWindow}${r.today_count > 0 ? `  ·  +${r.today_count} today` : ''} — click for trend & evidence`}
                    aria-label={`${r.name}: ${r.count} this ${velocityWindow}, ${r.today_count} today, trend ${r.trend} — view details`}>
                    <span className="ledger-vbar__track">
                      <span className="ledger-vbar__past" style={{ width: `${Math.max(0, Math.round((r.count - r.today_count) / maxCount * 100))}%`, background: color }} />
                      {r.today_count > 0 && (
                        <span className="ledger-vbar__todayseg" style={{ width: `${Math.max(5, Math.round(r.today_count / maxCount * 100))}%`, "--c": color }} />
                      )}
                    </span>
                    <span className="ledger-vbar__hint">details ›</span>
                  </button>
                )}
                <span className={`ledger-trend is-${r.trend}`} aria-hidden="true">
                  {r.state !== 'uncharted' && r.count > 0
                    ? ((r.trend === 'up_strong' || r.trend === 'up') ? '▲' : r.trend === 'down' ? '▼' : '·')
                    : ''}
                </span>
              </div>
              {open && (
                <PracticeGrid r={r} win={win} meta={meta} color={color} />
              )}
            </div>
          );
        })}
      </div>
      ) : (
        <LedgerFlat rows={rows} win={win} onLogDay={onLogDay}
          unanchored={(domainFilter && domainFilter.length) ? (ledger.unanchored || []).filter((p) => domainFilter.includes(p.domain)) : ledger.unanchored} />
      )}
      </div>
    </section>
  );
}

const COLUMN_DEFS = [
  { id: "acceptance", title: "Inbox",       sub: "New · drag or click to sort", eye: "INBOX", empty: "All clear" },
  { id: "focus_zone",    title: "Today",       sub: "Execute now",       eye: "TODAY",    empty: "Nothing scheduled — pull from Soon or capture" },
  { id: "deadline_bound",    title: "Soon",        sub: "Time-sensitive",    eye: "SOON",     empty: "Time-sensitive actions land here" },
  { id: "value_aligned", title: "Vision Board", sub: "Future intent",    eye: "VISION",   empty: "Future intent lives here" },
];

const HORIZON_TO_COL = { focus_zone: "focus_zone", deadline_bound: "deadline_bound", value_aligned: "value_aligned" };
const COL_TO_HORIZON = { focus_zone: "focus_zone", deadline_bound: "deadline_bound", value_aligned: "value_aligned" };
const ACTION_COLS = ["focus_zone", "deadline_bound", "value_aligned"];

const SORT_OPTIONS = [
  { key: "manual",    label: "Manual"       },
  { key: "alignment", label: "Alignment ↓"  },
  { key: "oldest",    label: "Oldest first" },
];

const CAPTURE_DOMAINS = ["physical", "inner", "creative", "interpersonal", "adventure", "admin"];

const DASH2_DOMAIN_ORDER = ['inner', 'physical', 'creative', 'interpersonal', 'adventure', 'admin'];
const DASH2_VALUE_VELOCITY_FALLBACK = [
  { label: 'HARM', percent: 42, domain: 'inner' },
  { label: 'BODY', percent: 15, domain: 'physical' },
];

function clampPct(n) {
  const v = Number.isFinite(n) ? n : 0;
  return Math.max(0, Math.min(100, Math.round(v)));
}

function renderValueVelocityRows(values) {
  if (!Array.isArray(values) || !values.length) return DASH2_VALUE_VELOCITY_FALLBACK;
  return values.slice(0, 6).map((v, idx) => {
    const pct = clampPct((v.velocity || v.aura_level || v.percent || 0) * 100);
    const domain = v.primary_domain || v.domain || DASH2_DOMAIN_ORDER[idx % DASH2_DOMAIN_ORDER.length];
    return {
      label: (v.name || v.label || 'Value').toUpperCase(),
      percent: pct,
      domain,
    };
  });
}

/* ── Date utilities (shared by CaptureModal + DueDatePrompt) ─────────────── */
const _pad  = n => String(n).padStart(2, '0');
const _fmt  = d => `${d.getFullYear()}-${_pad(d.getMonth()+1)}-${_pad(d.getDate())}`;
const _today = () => { const d = new Date(); d.setHours(0,0,0,0); return d; };
const _inSevenDays  = () => { const d = _today(); d.setDate(d.getDate() + 7);  return d; };
const _inThirtyDays = () => { const d = _today(); d.setDate(d.getDate() + 30); return d; };
const _relativeLabel = (dateStr) => {
  const d = new Date(dateStr + 'T00:00:00');
  const today = _today();
  const diff = Math.round((d - today) / 86400000);
  const abs = d.toLocaleDateString('en-US', {month: 'short', day: 'numeric'});
  if (diff < 0)  return `Overdue (${abs})`;
  if (diff === 0) return `Due today (${abs})`;
  if (diff === 1) return `Due tomorrow (${abs})`;
  return `Due in ${diff} days (${abs})`;
};
const parseMonthDay = (raw) => {
  const MONTHS = {jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12};
  const s = (raw || '').trim().toLowerCase();
  let month, day;
  const slashMatch = s.match(/^(\d{1,2})[/-](\d{1,2})$/);
  if (slashMatch) { month = +slashMatch[1]; day = +slashMatch[2]; }
  const alphaFirst = s.match(/^([a-z]{3,9})\s+(\d{1,2})$/);
  if (alphaFirst) { month = MONTHS[alphaFirst[1].slice(0,3)]; day = +alphaFirst[2]; }
  const alphaLast = s.match(/^(\d{1,2})\s+([a-z]{3,9})$/);
  if (alphaLast) { month = MONTHS[alphaLast[2].slice(0,3)]; day = +alphaLast[1]; }
  if (!month || !day || month < 1 || month > 12 || day < 1 || day > 31) return null;
  const today = _today();
  const candidate = new Date(today.getFullYear(), month - 1, day);
  if (candidate < today) candidate.setFullYear(today.getFullYear() + 1);
  return _fmt(candidate);
};



/* ── helpers ──────────────────────────────────────────────────────────────── */

function getDueDateState(due) {
  if (!due) return null;
  const today = new Date(); today.setHours(0,0,0,0);
  const d = new Date(due + 'T00:00:00');
  const delta = Math.round((d - today) / 86400000);
  if (delta < 0)  return 'overdue';
  if (delta === 0) return 'today';
  if (delta <= 2)  return 'impending';
  return 'healthy';
}

function formatDue(due, state) {
  if (!due) return '';
  if (state === 'today') return 'Due today';
  const d   = new Date(due + 'T00:00:00');
  const now = new Date(); now.setHours(0,0,0,0);
  const delta = Math.round((d - now) / 86400000);
  if (state === 'overdue') {
    const days = Math.abs(delta);
    if (days >= 14) return `Overdue by ${Math.round(days / 7)} weeks`;
    if (days >= 7)  return 'Overdue by 1 week';
    return days === 1 ? 'Overdue by 1 day' : `Overdue by ${days} days`;
  }
  if (state === 'impending') {
    return delta === 1 ? 'Due tomorrow' : `Due in ${delta} days`;
  }
  // healthy
  return delta === 1 ? 'Due tomorrow' : `Due in ${delta} days`;
}

/* ── DueDateChip ─────────────────────────────────────────────────────────── */

const DueDateChip = ({ due }) => {
  const state = getDueDateState(due);
  if (!state) return null;
  return (
    <span className={`task__due chip--${state}`} data-due-state={state} title={due}>
      {formatDue(due, state)}
    </span>
  );
};

/* ── TaskCard ────────────────────────────────────────────────────────────── */

const BODY_TRUNCATE = 140;

const TaskCard = ({ task, dragging, realizing, dismissing, onDragStart, onDragEnd,
                    onRealize, onDismiss, compact, onTitleClick, needsDate,
                    showPrompt, onDateResolved, onMove }) => {
  const dom      = TASK_DOMAINS[task.domain] || TASK_DOMAINS.admin;
  const inDrawer = task.column === "drawer";

  const rawBody = task.body || '';
  const truncatedBody = !compact && rawBody.length > BODY_TRUNCATE
    ? rawBody.slice(0, BODY_TRUNCATE) + '…'
    : rawBody;

  const [editingDate, setEditingDate] = useState(false);
  const trayOpen = !inDrawer && (showPrompt || editingDate);

  const openDrawer = (e) => {
    e.stopPropagation();
    if (!inDrawer) onTitleClick && onTitleClick(task);
  };

  return (
    <article
      className={
        "task" +
        (dragging    ? " is-dragging"    : "") +
        (realizing   ? " is-realizing"   : "") +
        (dismissing  ? " is-dismissing"  : "") +
        (inDrawer    ? " is-archived"    : "") +
        (compact     ? " is-compact"     : "") +
        (task.completed ? " is-completed" : "") +
        (!inDrawer && task.score >= 0.85 ? " is-high-resonance" : "") +
        (needsDate ? " needs-date" : "")
      }
      draggable={!realizing && !dismissing && !inDrawer}
      tabIndex={0}
      role="listitem"
      aria-label={`${dom.label} action: ${task.title}`}
      data-task-id={task.id}
      data-due-state={getDueDateState(task.due) || ''}
      onDragStart={(e) => onDragStart && onDragStart(e, task)}
      onDragEnd={onDragEnd}
      onKeyDown={(e) => {
        if (inDrawer) return;
        if (e.key === " " || e.key === "Enter") {
          e.preventDefault();
          onRealize && onRealize(task.id);
        } else if (e.key === "ArrowRight") {
          e.preventDefault();
          onMove && onMove(task.id, 1);
        } else if (e.key === "ArrowLeft") {
          e.preventDefault();
          onMove && onMove(task.id, -1);
        }
      }}
    >
      <span className="task__strip" style={{ background: dom.color }} aria-hidden="true" />

      {!inDrawer && task.score >= 0.85 && (
        <span className="task__north" title="High resonance · True North aligned">
          <span className="task__north-ico" aria-hidden="true">✦</span>
          Aligned
        </span>
      )}

      {!inDrawer ? (
        <button
          className="task__realize"
          onClick={(e) => { e.stopPropagation(); onRealize && onRealize(task.id); }}
          aria-label={task.completed ? `Undo: ${task.title}` : `Realize: ${task.title}`}
          title={task.completed ? "Undo · mark as not done" : "Realize · mark as done"}
          aria-pressed={!!task.completed}
        >
          <span className="task__realize-ring" aria-hidden="true" />
          <svg className="task__realize-check" width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
            <path d="M4 9.5 L7.5 13 L14 5.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
          <svg className="task__realize-undo" width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
            <path d="M3 9 A6 6 0 1 0 5 4.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
            <path d="M3 3 L3 7 L7 7" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </button>
      ) : (
        <span className={"task__realize " + (task.folder === "realized" ? "task__realize--done" : "task__realize--dismissed")} aria-hidden="true">
          {task.folder === "realized" ? (
            <svg className="task__realize-check" width="18" height="18" viewBox="0 0 18 18">
              <path d="M4 9.5 L7.5 13 L14 5.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          ) : (
            <svg className="task__realize-check" width="18" height="18" viewBox="0 0 18 18">
              <path d="M5 5 L13 13 M13 5 L5 13" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
            </svg>
          )}
        </span>
      )}

      {/* Body — entire area is the drawer-open target on active cards */}
      <div
        className={"task__body" + (!inDrawer ? " task__body--open" : "")}
        onClick={openDrawer}
      >
        <h4 className="task__title card-title">{task.title}</h4>
        {!inDrawer && <span className="task__open-hint" aria-hidden="true">›</span>}
        {!compact && truncatedBody && (
          <p className="task__text">{truncatedBody}</p>
        )}
      </div>
      {!inDrawer && (
        <div className="task__footer" onClick={e => e.stopPropagation()}>
          {trayOpen ? (
            <DueDatePrompt
              task={task}
              onResolve={(date) => {
                setEditingDate(false);
                onDateResolved && onDateResolved(task.id, date);
              }}
            />
          ) : task.due ? (
            <DueDateChip due={task.due} />
          ) : needsDate ? (
            <button className="task__add-date" onClick={() => setEditingDate(true)}>+ add date</button>
          ) : null}
        </div>
      )}
    </article>
  );
};

/* ── DropSlot ─────────────────────────────────────────────────────────────── */
const DropSlot = ({ active }) => (
  <div className={"drop-slot" + (active ? " is-active" : "")} aria-hidden="true" />
);

/* ── SortControl ──────────────────────────────────────────────────────────── */

const SortControl = ({ colId, value, onChange }) => {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);

  const active = SORT_OPTIONS.find(o => o.key === value) || SORT_OPTIONS[0];
  const isNonManual = value !== 'manual';

  return (
    <div className="col__sort-wrap" ref={ref}>
      {isNonManual ? (
        <button className="col__sort-chip" onClick={() => setOpen(v => !v)} title="Change sort">
          {active.label} ⇅
        </button>
      ) : (
        <button className="col__sort-btn" onClick={() => setOpen(v => !v)} title="Sort column" aria-label="Sort">
          ⇅
        </button>
      )}
      {open && (
        <div className="col__sort-menu">
          {SORT_OPTIONS.map(opt => (
            <button
              key={opt.key}
              className={"col__sort-opt" + (opt.key === value ? " is-on" : "")}
              onClick={() => { onChange(opt.key); setOpen(false); }}
            >{opt.label}</button>
          ))}
        </div>
      )}
    </div>
  );
};

/* ── Column ───────────────────────────────────────────────────────────────── */

const SCROLL_EDGE = 64;
const SCROLL_SPEED = 12;

const Column = ({ def, tasks, dragOver, draggingId, realizingId, dismissingId,
                  onDragOver, onDragLeave, onDrop, onDragStart, onDragEnd,
                  onRealize, onDismiss, onAddInColumn, activeSlot, isDragging,
                  onSlotChange, onTitleClick, sortKey, onSortChange,
                  pendingDateId, onDateResolved, headerExtra,
                  isExpanded, isCollapsed, onToggleExpand, onMove, practices = [] }) => {
  const isEmpty = tasks.length === 0 && practices.length === 0;
  const listRef = useRef(null);
  const rafRef  = useRef(null);
  const dirRef  = useRef(0);

  const stopAutoScroll = () => {
    if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; }
    dirRef.current = 0;
  };
  const tickAutoScroll = () => {
    const el = listRef.current;
    if (el && dirRef.current !== 0) {
      el.scrollTop += dirRef.current * SCROLL_SPEED;
      rafRef.current = requestAnimationFrame(tickAutoScroll);
    } else { stopAutoScroll(); }
  };
  const handleDragOver = (e) => {
    onDragOver(e);
    const el = listRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const y = e.clientY;
    let dir = 0;
    if (y - rect.top < SCROLL_EDGE && el.scrollTop > 0) dir = -1;
    else if (rect.bottom - y < SCROLL_EDGE && el.scrollTop < el.scrollHeight - el.clientHeight) dir = 1;
    if (dir !== dirRef.current) {
      dirRef.current = dir;
      if (dir !== 0 && !rafRef.current) rafRef.current = requestAnimationFrame(tickAutoScroll);
    }
    if (onSlotChange) {
      const cards = Array.from(el.querySelectorAll('[data-task-id]'));
      let slot = cards.length;
      for (let i = 0; i < cards.length; i++) {
        const r = cards[i].getBoundingClientRect();
        if (y < r.top + r.height / 2) { slot = i; break; }
      }
      onSlotChange(slot);
    }
  };
  const handleDragLeave = (e) => { onDragLeave(e); stopAutoScroll(); };
  const handleDrop = (e) => { onDrop(e); stopAutoScroll(); };
  useEffect(() => () => stopAutoScroll(), []);

  return (
    <section
      className={"col col--" + def.id + (dragOver ? " is-drag-over" : "") + (isExpanded ? " is-expanded" : "") + (isCollapsed ? " is-collapsed" : "")}
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
      aria-label={def.title}
    >
      <header className="col__head">
        <div className="col__title-row">
          <h3 className="col__title column-title">
            {def.title} ({tasks.length + practices.length})
          </h3>
          <div className="col__head-controls">
            <SortControl colId={def.id} value={sortKey} onChange={onSortChange} />
            {onAddInColumn && (
              <button
                className="col__add-btn"
                onClick={() => onAddInColumn(def.id)}
                title={`Add to ${def.title}`}
              >
                <svg width="11" height="11" viewBox="0 0 12 12" aria-hidden="true">
                  <path d="M6 1.5 L6 10.5 M1.5 6 L10.5 6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/>
                </svg>
                Add Task
              </button>
            )}
            {onToggleExpand && (
              <button
                className={"col__expand-btn" + (isExpanded ? " is-on" : "")}
                onClick={() => onToggleExpand(def.id)}
                aria-expanded={!!isExpanded}
                title={isExpanded ? `Collapse ${def.title}` : `Expand ${def.title}`}
              >
                {isExpanded ? (
                  <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor"
                    strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                    <path d="M6 2v4H2M10 14v-4h4M6 6 2 2M10 10l4 4" />
                  </svg>
                ) : (
                  <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor"
                    strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                    <path d="M9 2h5v5M14 2 9 7M7 14H2V9M2 14l5-5" />
                  </svg>
                )}
              </button>
            )}
          </div>
        </div>
        <span className="col__sub"><em>{def.sub}</em></span>
      </header>
      {headerExtra || null}
      <div className="col__list" role="list" ref={listRef}>
        {isEmpty && !isDragging && <div className="col__empty">{def.empty || 'Nothing here yet'}</div>}
        {practices.map(pr => {
          const pm = TASK_DOMAINS[pr.domain] || TASK_DOMAINS.admin;
          return (
            <div key={pr.id} className="practice-chip" role="listitem" title={`${pm.label} practice`}>
              <span className="practice-chip__glyph" aria-hidden="true"
                style={{"--ico": `url(/dashboard-ui/assets/icons/${pm.ico}.svg)`, background: pm.color}} />
              <span className="practice-chip__name">{pr.name}</span>
            </div>
          );
        })}
        {isDragging && <DropSlot active={activeSlot === 0} />}
        {tasks.map((t, idx) => (
          <React.Fragment key={t.id}>
            <TaskCard
              task={t}
              dragging={draggingId === t.id}
              realizing={realizingId === t.id}
              dismissing={dismissingId === t.id}
              onDragStart={onDragStart}
              onDragEnd={onDragEnd}
              onRealize={onRealize}
              onDismiss={onDismiss}
              onTitleClick={onTitleClick}
              needsDate={def.id === 'deadline_bound' && !t.due && t.id !== pendingDateId}
              showPrompt={pendingDateId === t.id}
              onDateResolved={onDateResolved}
              onMove={onMove}
            />
            {isDragging && <DropSlot active={activeSlot === idx + 1} />}
          </React.Fragment>
        ))}
      </div>
    </section>
  );
};

/* ── CalendarPicker ──────────────────────────────────────────────────────── */

const _MONTH_NAMES = ['January','February','March','April','May','June',
                      'July','August','September','October','November','December'];
const _DAY_HEADS   = ['Su','Mo','Tu','We','Th','Fr','Sa'];

const CalendarPicker = ({ value, onPick }) => {
  const today = _today();
  const init  = value ? new Date(value + 'T00:00:00') : today;
  const [vy, setVy] = useState(init.getFullYear());
  const [vm, setVm] = useState(init.getMonth());

  const firstDow    = new Date(vy, vm, 1).getDay();
  const daysInMonth = new Date(vy, vm + 1, 0).getDate();
  const cells = Array(firstDow).fill(null).concat(
    Array.from({length: daysInMonth}, (_, i) => i + 1)
  );

  const prev = () => vm === 0  ? (setVm(11), setVy(y => y - 1)) : setVm(m => m - 1);
  const next = () => vm === 11 ? (setVm(0),  setVy(y => y + 1)) : setVm(m => m + 1);

  return (
    <div className="cal-picker" onClick={e => e.stopPropagation()}>
      <div className="cal-picker__nav">
        <button className="cal-picker__nav-btn" onClick={prev} type="button">‹</button>
        <span className="cal-picker__month-label">{_MONTH_NAMES[vm]} {vy}</span>
        <button className="cal-picker__nav-btn" onClick={next} type="button">›</button>
      </div>
      <div className="cal-picker__grid">
        {_DAY_HEADS.map(d => <span key={d} className="cal-picker__day-head">{d}</span>)}
        {cells.map((day, i) => {
          if (!day) return <span key={`e${i}`} />;
          const dateStr = _fmt(new Date(vy, vm, day));
          const isPast  = new Date(vy, vm, day) < today;
          return (
            <button key={day} type="button"
              className={"cal-picker__day" +
                (dateStr === value  ? " is-selected" : "") +
                (dateStr === _fmt(today) ? " is-today"    : "") +
                (isPast             ? " is-past"     : "")}
              onClick={() => !isPast && onPick(dateStr)}
              disabled={isPast}
            >{day}</button>
          );
        })}
      </div>
    </div>
  );
};

/* ── DueDatePrompt ───────────────────────────────────────────────────────── */

const DueDatePrompt = ({ task, onResolve }) => {
  const [showCustom, setShowCustom] = useState(false);
  const [customVal,  setCustomVal]  = useState('');
  const [parseErr,   setParseErr]   = useState(false);
  const inputRef = useRef(null);
  useEffect(() => { if (showCustom) setTimeout(() => inputRef.current?.focus(), 50); }, [showCustom]);

  const commitCustom = () => {
    const parsed = parseMonthDay(customVal);
    if (!parsed) { setParseErr(true); return; }
    setParseErr(false); onResolve(parsed);
  };

  return (
    <div className="due-prompt" onClick={e => e.stopPropagation()}>
      <span className="due-prompt__label">Set a deadline</span>
      <div className="due-prompt__chips">
        <button className="due-prompt__chip" onClick={() => onResolve(_fmt(_inSevenDays()))}>
          <span className="due-prompt__chip-label">This Week</span>
          <span className="due-prompt__chip-sub">in 7 days</span>
        </button>
        <button className="due-prompt__chip" onClick={() => onResolve(_fmt(_inThirtyDays()))}>
          <span className="due-prompt__chip-label">This Month</span>
          <span className="due-prompt__chip-sub">in 30 days</span>
        </button>
        <button className="due-prompt__chip due-prompt__chip--date"
          onClick={() => { setShowCustom(v => !v); setParseErr(false); }}>
          <span className="due-prompt__chip-label">{showCustom ? 'Close' : 'Pick date'}</span>
          <span className="due-prompt__chip-sub">📅 calendar</span>
        </button>
      </div>
      {showCustom && (
        <CalendarPicker value={null} onPick={(d) => { onResolve(d); }} />
      )}
      <button className="due-prompt__skip" onClick={() => onResolve(null)}>Skip for now</button>
    </div>
  );
};

/* ── ArchiveFolder (paginated) ───────────────────────────────────────────── */

const PAGE = 5;

const ArchiveFolder = ({ eye, items, onRestore }) => {
  const [limit, setLimit] = useState(PAGE);
  const visible = items.slice(0, limit);
  const hasMore = items.length > limit;
  return (
    <div className="archive-folder">
      <header className="archive-folder__head">
        <span className="archive-folder__eye">{eye}</span>
        <span className="archive-folder__count">{items.length}</span>
      </header>
      <div className="archive-folder__list">
        {items.length === 0 && <p className="archive-folder__empty"><em>Nothing here.</em></p>}
        {visible.map(a => (
          <div key={a.action_id} className="archive-item">
            <span className="archive-item__title">{a.ai_refined_title}</span>
            <button className="archive-item__restore" onClick={() => onRestore(a.action_id)}>
              Restore
            </button>
          </div>
        ))}
        {hasMore && (
          <button className="archive-folder__show-more"
            onClick={() => setLimit(l => l + PAGE)}>
            Show {Math.min(PAGE, items.length - limit)} more
          </button>
        )}
      </div>
    </div>
  );
};

/* ── ArchiveDrawer ────────────────────────────────────────────────────────── */

const ArchiveDrawer = ({ onRestore, onDismiss }) => {
  const [open,     setOpen]    = useState(false);
  const [data,     setData]    = useState(null);
  const [loading,  setLoading] = useState(false);
  const [dragOver, setDragOver] = useState(false);

  const load = () => {
    setLoading(true);
    fetch('/api/actions/archive')
      .then(r => r.ok ? r.json() : {realized: [], dismissed: []})
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  };

  const handleOpen = () => { setOpen(true); load(); };
  const handleToggle = (e) => {
    // clicking the panel itself shouldn't close it
    if (e.target.closest('.archive-panel')) return;
    open ? setOpen(false) : handleOpen();
  };

  const handleRestore = (id) => {
    fetch(`/api/actions/${id}/restore`, {method: 'POST'}).catch(() => {});
    setData(prev => {
      if (!prev) return prev;
      const filter = arr => arr.filter(a => a.action_id !== id);
      return {realized: filter(prev.realized), dismissed: filter(prev.dismissed)};
    });
    onRestore && onRestore(id);
  };

  // Drag-to-archive handlers
  const handleDragOver = (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDragOver(true); };
  const handleDragLeave = (e) => { if (!e.currentTarget.contains(e.relatedTarget)) setDragOver(false); };
  const handleDrop = (e) => {
    e.preventDefault();
    setDragOver(false);
    const id = e.dataTransfer.getData('text/plain');
    if (id) onDismiss && onDismiss(id);
  };

  const realized  = data?.realized  || [];
  const dismissed = data?.dismissed || [];
  const total = dismissed.length;

  return (
    <section
      className={"archive-col" + (dragOver ? " is-drag-over" : "")}
      aria-label="Bucket List"
      onClick={handleToggle}
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
      style={{cursor: 'pointer'}}
    >
      <button
        className="archive-col__chev"
        onClick={e => { e.stopPropagation(); open ? setOpen(false) : handleOpen(); }}
        aria-expanded={open}
        aria-label={open ? "Close Bucket List" : "Open Bucket List"}
      >
        <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
          <path d={open ? "M9 1 L3 7 L9 13" : "M5 1 L11 7 L5 13"} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
        </svg>
      </button>
      <span className="archive-col__title">Bucket List</span>
      <div className="archive-col__meta">
        <span className="archive-col__count"><strong>{total}</strong></span>
      </div>
      <div className="archive-col__drop">
        <span className="archive-col__drop-ico">{dragOver ? "↓" : "→"}</span>
        <span className="archive-col__drop-lbl">Drop to Bucket List</span>
      </div>

      {open && (
        <>
          <div className="archive-overlay" onClick={e => { e.stopPropagation(); setOpen(false); }} />
          <div className="archive-panel" role="dialog" aria-label="Bucket List" onClick={e => e.stopPropagation()}>
            <header className="archive-panel__head">
              <div>
                <span className="label-caps" style={{color: 'var(--gold-1)'}}>Bucket List · Subconscious Storage</span>
                <h3 className="archive-panel__title">{total} items</h3>
              </div>
              <button className="archive-panel__close" onClick={() => setOpen(false)} aria-label="Close">×</button>
            </header>
            {loading && <p style={{padding: '12px 20px', opacity: 0.5}}><em>Loading…</em></p>}
            {!loading && (
              <div className="archive-folders">
                <ArchiveFolder eye="BUCKET LIST" items={dismissed} onRestore={handleRestore} />
              </div>
            )}
          </div>
        </>
      )}
    </section>
  );
};

/* ── CaptureModal ─────────────────────────────────────────────────────────── */

const CaptureModal = ({ initialColumn, initialType, onCommit, onClose, onPracticeCreated }) => {
  const [type, setType]             = useState(initialType || "action");
  const [name, setName]             = useState("");
  const [description, setDescription] = useState("");
  const [dueDate, setDueDate]       = useState(
    initialColumn === "focus_zone" ? "today" : initialColumn === "deadline_bound" ? "date" : "none"
  );
  const [dueDateText, setDueDateText] = useState("");
  const [dateParseErr, setDateParseErr] = useState(false);
  const [domain, setDomain]         = useState("admin");
  const [valueAligned, setValueAligned] = useState(false);
  const [autoMsg, setAutoMsg]       = useState(null);
  const [autoFading, setAutoFading] = useState(false);
  const nameRef = useRef(null);

  const commit = () => {
    if (!name.trim()) return;
    if (type === "practice") {
      const icon = (TASK_DOMAINS[domain] || TASK_DOMAINS.admin).ico;
      fetch('/api/momentum/registry', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({ name: name.trim(), domain, icon }),
      })
        .then(r => r.ok ? r.json() : null)
        .then(() => { onPracticeCreated && onPracticeCreated(); })
        .catch(() => {});
      onClose();
      return;
    }
    const effDueDate = type === "log" ? "today" : dueDate;
    const col = effDueDate === "today" ? "focus_zone" : effDueDate === "date" ? "deadline_bound" : "value_aligned";
    const horizonState = COL_TO_HORIZON[col] || "focus_zone";
    const tempId = `pending_${Date.now()}`;
    onCommit({
      id: tempId,
      column: col,
      domain,
      title: name.trim(),
      body: description.trim(),
      segment: name.trim(),
      score: valueAligned ? 0.95 : 0.55,
      staleDays: 0,
      completed: false,
      due: effDueDate === "date" && dueDateText ? (parseMonthDay(dueDateText) || dueDateText) : undefined,
    });
    fetch('/api/actions', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({
        ai_refined_title: name.trim(),
        ai_refined_description: description.trim() || null,
        origin_snippet: name.trim(),
        domain,
        score: valueAligned ? 0.95 : 0.55,
        horizon_state: horizonState,
        due_date: effDueDate === "date" && dueDateText ? (parseMonthDay(dueDateText) || null) : null,
      }),
    }).catch(() => {});
    onClose();
  };

  useEffect(() => { nameRef.current?.focus(); }, []);
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); commit(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  });

  const autoAlign = () => {
    const text = (name + " " + description).toLowerCase();
    const rules = [
      {dom:"physical",      words:["sauna","training","walk","gym","workout","run","lift"]},
      {dom:"inner",         words:["meditate","meditation","journal","read","stillness","reflect"]},
      {dom:"creative",      words:["draft","write","sketch","compose","create","make","manuscript"]},
      {dom:"interpersonal", words:["call","meet","connect","talk","reply","bond","dinner"]},
      {dom:"adventure",     words:["explore","wander","trip","drive","travel","expedition"]},
      {dom:"admin",         words:["file","reconcile","review","admin","schedule","email","invoice"]},
    ];
    let matched = "admin";
    for (const r of rules) { if (r.words.some(w => text.includes(w))) { matched = r.dom; break; } }
    const aligned = /(ritual|core|true north|sovereignty|values?|magnetic north)/.test(text);
    setDomain(matched); setValueAligned(aligned);
    setAutoMsg(`System detected · ${TASK_DOMAINS[matched].label}${aligned ? " · aligned with core values" : ""}`);
    setAutoFading(false);
    setTimeout(() => setAutoFading(true), 2400);
    setTimeout(() => setAutoMsg(null), 3200);
  };

  return (
    <div className="capture-veil" onClick={onClose}>
      <div className="capture" onClick={e => e.stopPropagation()} role="dialog" aria-modal="true">
        <header className="capture__head">
          <span className="label-caps capture__eye">Capture</span>
          <p className="capture__lede"><em>Define the vector of your energy.</em></p>
        </header>
        <div className="capture__type">
          {[["action","Action"],["log","Daily Log"],["practice","Practice"]].map(([v,lbl]) => (
            <button key={v} type="button"
              className={"capture__type-pill " + (type === v ? "is-active" : "")}
              onClick={() => setType(v)}>{lbl}</button>
          ))}
        </div>
        <div className="capture__field">
          <label className="capture__label">Action Name <span className="capture__req">required</span></label>
          <input ref={nameRef} className="capture__input capture__input--name" placeholder="What requires execution?"
            value={name} onChange={e => setName(e.target.value)}/>
        </div>
        <div className="capture__field">
          <label className="capture__label">Description <span className="capture__opt">optional</span></label>
          <textarea className="capture__input capture__input--desc" rows="2"
            placeholder="Context, constraints, or resonance…"
            value={description} onChange={e => setDescription(e.target.value)}/>
        </div>
        {type === "action" && (
        <div className="capture__field">
          <label className="capture__label">Horizon · routes the action</label>
          <div className="capture__pills">
            {[["today","Today","Current Heading"],["date","Specific date","Future Horizon"],["none","No deadline","Intentions"]].map(([v,tag,dest]) => (
              <button key={v} className={"capture__pill " + (dueDate === v ? "is-on" : "")}
                onClick={() => setDueDate(v)} type="button">
                <span className="capture__pill-tag">{tag}</span>
                <span className="capture__pill-dest">{dest}</span>
              </button>
            ))}
          </div>
          {dueDate === "date" && (
            <div className="capture__date-picker">
              <div className="capture__date-chips">
                <button type="button" className="capture__date-chip"
                  onClick={() => { setDueDateText(_fmt(_inSevenDays())); setDateParseErr(false); }}>
                  <span className="capture__chip-label">This Week</span>
                  <span className="capture__chip-sub">in 7 days</span>
                </button>
                <button type="button" className="capture__date-chip"
                  onClick={() => { setDueDateText(_fmt(_inThirtyDays())); setDateParseErr(false); }}>
                  <span className="capture__chip-label">This Month</span>
                  <span className="capture__chip-sub">in 30 days</span>
                </button>
              </div>
              <div className="capture__date-input-row">
                <input
                  type="text"
                  className={"capture__input capture__date-input" + (dateParseErr ? " is-error" : "")}
                  placeholder="Jun 15  or  6/15"
                  value={dueDateText}
                  onChange={e => { setDueDateText(e.target.value); setDateParseErr(false); }}
                  onBlur={() => {
                    if (dueDateText && !parseMonthDay(dueDateText)) setDateParseErr(true);
                    else setDateParseErr(false);
                  }}
                />
              </div>
              {dateParseErr && <span className="capture__date-err">Try: Jun 15 or 6/15</span>}
            </div>
          )}
        </div>
        )}
        <button className="capture__autoalign" onClick={autoAlign} type="button">
          <span className="capture__autoalign-glyph" aria-hidden="true">✦</span>
          Auto-Align with System
        </button>
        {autoMsg && <p className={"capture__autoalign-msg" + (autoFading ? " is-fading" : "")}><em>{autoMsg}</em></p>}
        <div className="capture__overrides">
          <span className="label-caps capture__overrides-eye">Manual Overrides</span>
          <div className="capture__field">
            <label className="capture__label">Domain</label>
            <div className="capture__domains">
              {CAPTURE_DOMAINS.map(k => {
                const d = TASK_DOMAINS[k];
                return (
                  <button key={k} type="button"
                    className={"capture__domain-chip " + (domain === k ? "is-on" : "")}
                    style={{"--chip-color": d.color}} onClick={() => setDomain(k)}>
                    <span className="capture__domain-strip" aria-hidden="true"/>
                    <span className="capture__domain-label">{d.label}</span>
                  </button>
                );
              })}
            </div>
          </div>
          <div className="capture__field capture__field--align">
            <label className="capture__label">Value-Aligned</label>
            <button type="button" className={"capture__align-toggle " + (valueAligned ? "is-on" : "")}
              onClick={() => setValueAligned(v => !v)} aria-pressed={valueAligned}>
              <span className="capture__align-box" aria-hidden="true">
                {valueAligned && <svg width="12" height="12" viewBox="0 0 12 12">
                  <path d="M2.5 6.5 L5 9 L9.5 3.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>}
              </span>
              <span>Lock to True North</span>
              {valueAligned && <span className="capture__align-badge" aria-hidden="true"><span>✦</span> Aligned</span>}
            </button>
          </div>
        </div>
        <footer className="capture__foot">
          <button className="capture__dismiss" type="button" onClick={onClose}>Dismiss</button>
          <button className="capture__commit" type="button" onClick={commit} disabled={!name.trim()}>
            Commit to System <span className="capture__kbd" aria-hidden="true">⌘ ↵</span>
          </button>
        </footer>
      </div>
    </div>
  );
};

/* ── SegmentContext ──────────────────────────────────────────────────────── */

// Chars of surrounding text shown in expanded paragraph view
const SC_BEFORE_CHARS = 350;
const SC_AFTER_CHARS  = 200;

const SegmentContext = ({ actionId }) => {
  const [ctx,      setCtx]      = React.useState(null);
  const [expanded, setExpanded] = React.useState(false);

  React.useEffect(() => {
    if (!actionId) return;
    setExpanded(false);
    fetch(`/api/actions/${actionId}/context`)
      .then(r => r.ok ? r.json() : null)
      .then(setCtx)
      .catch(() => {});
  }, [actionId]);

  if (!ctx || !ctx.highlight) return null;

  // Date label: "DAILY LOG M/D/YY"
  const dateLabel = (() => {
    if (!ctx.segment_date) return 'DAILY LOG';
    const d = new Date(ctx.segment_date);
    if (isNaN(d)) return 'DAILY LOG';
    return `DAILY LOG ${d.getMonth()+1}/${d.getDate()}/${String(d.getFullYear()).slice(-2)}`;
  })();

  // Trim surrounding text for the expanded inline view
  const beforeRaw  = ctx.before_highlight || '';
  const afterRaw   = ctx.after_highlight  || '';
  const beforeText = beforeRaw.length > SC_BEFORE_CHARS
    ? '…' + beforeRaw.slice(-SC_BEFORE_CHARS).trimStart()
    : beforeRaw;
  const afterText  = afterRaw.length > SC_AFTER_CHARS
    ? afterRaw.slice(0, SC_AFTER_CHARS).trimEnd() + '…'
    : afterRaw;

  const hasContext = beforeText || afterText;

  return (
    <div className="segment-context">
      <span className="label-caps segment-context__eye">{dateLabel}</span>

      {!expanded ? (
        // Default: just the relevant excerpt in a blockquote
        <blockquote className="segment-context__highlight-block">
          {ctx.highlight}
        </blockquote>
      ) : (
        // Expanded: full paragraph with the relevant portion underlined inline
        <p className="segment-context__full-text">
          {beforeText}
          <u className="segment-context__inline-highlight">{ctx.highlight}</u>
          {afterText}
        </p>
      )}

      {hasContext && (
        <button
          className="segment-context__toggle"
          onClick={() => setExpanded(e => !e)}
          type="button"
        >
          {expanded ? 'Show less' : 'Show context'}
        </button>
      )}
    </div>
  );
};

/* ── DetailDrawer (Module 5) ─────────────────────────────────────────────── */

const DetailDrawer = ({ task, onClose, onTaskUpdate, onDone, onArchive, onDelete }) => {
  const [title, setTitle]       = useState(task.title || '');
  const [desc, setDesc]         = useState(task.body  || '');
  const [saveState, setSaveState] = useState('idle'); // idle | saving | saved
  const [updates, setUpdates]   = useState(null);
  const [newNote, setNewNote]   = useState('');
  const [deleteConfirm, setDeleteConfirm] = useState(false);
  const [dueDate,  setDueDate]  = useState(task.due || null);
  const [showCal,  setShowCal]  = useState(false);
  const debounceRef = useRef(null);
  const latestTitle = useRef(title);
  const latestDesc  = useRef(desc);
  const drawerRef   = useRef(null);

  // Close on click outside the drawer panel
  useEffect(() => {
    const handler = (e) => {
      if (drawerRef.current && !drawerRef.current.contains(e.target)) onClose();
    };
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('mousedown', handler);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', handler);
      document.removeEventListener('keydown', onKey);
    };
  }, [onClose]);

  useEffect(() => { latestTitle.current = title; }, [title]);
  useEffect(() => { latestDesc.current  = desc;  }, [desc]);

  // Load updates on mount
  useEffect(() => {
    fetch(`/api/actions/${task.id}/updates`)
      .then(r => r.ok ? r.json() : [])
      .then(setUpdates)
      .catch(() => setUpdates([]));
  }, [task.id]);

  const save = useCallback((t, d) => {
    setSaveState('saving');
    fetch(`/api/actions/${task.id}`, {
      method: 'PATCH',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({ai_refined_title: t, ai_refined_description: d}),
    })
    .then(() => { setSaveState('saved'); setTimeout(() => setSaveState('idle'), 1500); })
    .catch(() => setSaveState('idle'));
    onTaskUpdate && onTaskUpdate(task.id, {title: t, body: d});
  }, [task.id]);

  const scheduleSave = (t, d) => {
    clearTimeout(debounceRef.current);
    setSaveState('saving');
    debounceRef.current = setTimeout(() => save(t, d), 500);
  };

  // Force sync on unmount
  useEffect(() => () => {
    clearTimeout(debounceRef.current);
    const t = latestTitle.current;
    const d = latestDesc.current;
    if (t !== task.title || d !== task.body) {
      fetch(`/api/actions/${task.id}`, {
        method: 'PATCH',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({ai_refined_title: t, ai_refined_description: d}),
      }).catch(() => {});
    }
  }, [task.id]);

  const addUpdate = () => {
    const body = newNote.trim();
    if (!body) return;
    fetch(`/api/actions/${task.id}/updates`, {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({body_text: body}),
    })
    .then(r => r.ok ? r.json() : null)
    .then(u => { if (u) setUpdates(prev => [...(prev || []), u]); })
    .catch(() => {});
    setNewNote('');
  };

  const saveDue = (date) => {
    setDueDate(date);
    setShowCal(false);
    fetch(`/api/actions/${task.id}`, {
      method: 'PATCH',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({due_date: date || null}),
    }).catch(() => {});
    onTaskUpdate && onTaskUpdate(task.id, {due: date});
  };

  const dom = TASK_DOMAINS[task.domain] || TASK_DOMAINS.admin;

  return (
    <>
      <div className="drawer-backdrop" aria-hidden="true"/>
      <aside className="detail-drawer" role="dialog" aria-label="Action Detail" ref={drawerRef}>
        <div className="detail-drawer__inner">

          {/* Pinned topbar */}
          <div className="detail-drawer__topbar detail-drawer__topbar--pinned">
            <span className="detail-drawer__eyebrow">TASK DETAIL</span>
            <button className="detail-drawer__close" onClick={onClose} aria-label="Close drawer">×</button>
          </div>

          {/* Everything else scrolls */}
          <div className="detail-drawer__scroll-body">

          {/* Title + origin + description */}
          <div className="detail-drawer__zone detail-drawer__zone--header">
            <div className="detail-drawer__title-row">
              <span className="detail-drawer__domain-dot" style={{background: dom.color}} title={dom.label}/>
              <input
                className="detail-drawer__title-input"
                value={title}
                onChange={e => { setTitle(e.target.value); scheduleSave(e.target.value, latestDesc.current); }}
                placeholder="Action title…"
              />
            </div>
            {saveState === 'saving' && <span className="detail-drawer__save-state">Saving…</span>}
            {saveState === 'saved'  && <span className="detail-drawer__save-state is-saved">Saved ✓</span>}

            <SegmentContext actionId={task.id} taskTitle={task.title} />

            <textarea
              className="detail-drawer__desc-input"
              value={desc}
              rows={3}
              onChange={e => { setDesc(e.target.value); scheduleSave(latestTitle.current, e.target.value); }}
              placeholder="Add description…"
            />
          </div>

          {/* Due date */}
          <div className="detail-drawer__zone detail-drawer__zone--due">
            <span className="detail-drawer__zone-eye">DEADLINE</span>

            {/* Persistent chip row — always visible */}
            <div className="detail-drawer__due-chips">
              {[
                { label: 'TODAY',      sub: null,          date: () => _fmt(_today())         },
                { label: 'THIS WEEK',  sub: 'in 7 days',   date: () => _fmt(_inSevenDays())   },
                { label: 'THIS MONTH', sub: 'in 30 days',  date: () => _fmt(_inThirtyDays())  },
                { label: 'PICK A DATE', sub: '📅', date: null                        },
              ].map(({ label, sub, date }) => {
                const isCustomActive = label === 'PICK A DATE' && dueDate
                  && dueDate !== _fmt(_today())
                  && dueDate !== _fmt(_inSevenDays())
                  && dueDate !== _fmt(_inThirtyDays());
                const isPresetActive = date && dueDate === date();
                const active = isPresetActive || isCustomActive;
                return (
                  <button
                    key={label}
                    type="button"
                    className={"detail-drawer__due-chip" + (active ? " detail-drawer__due-chip--active" : "")}
                    onClick={() => date ? saveDue(date()) : setShowCal(v => !v)}
                  >
                    {label}
                    {sub && <span className="detail-drawer__due-chip-sub">{sub}</span>}
                  </button>
                );
              })}
            </div>

            {showCal && (
              <CalendarPicker value={dueDate} onPick={(d) => { saveDue(d); setShowCal(false); }} />
            )}

            {/* Feedback — below the chips */}
            {dueDate ? (
              <div className="detail-drawer__due-feedback">
                <span className="detail-drawer__due-feedback-label">{_relativeLabel(dueDate)}</span>
                <button className="detail-drawer__due-clear" type="button"
                  onClick={() => saveDue(null)}>Clear Deadline</button>
              </div>
            ) : (
              <p className="detail-drawer__due-placeholder">
                No deadline assigned · Select if you want to add a deadline
              </p>
            )}
          </div>

          {/* Updates timeline */}
          <div className="detail-drawer__zone detail-drawer__zone--updates">
            <span className="label-caps detail-drawer__updates-eye">Updates</span>
            <div className="detail-drawer__timeline">
              {updates === null && <p className="detail-drawer__timeline-empty"><em>Loading…</em></p>}
              {updates && updates.length === 0 && <p className="detail-drawer__timeline-empty"><em>No updates yet.</em></p>}
              {updates && updates.map(u => (
                <div key={u.update_id} className="detail-drawer__update">
                  <span className="detail-drawer__update-date">{u.created_at ? u.created_at.slice(0,16).replace('T',' ') : ''}</span>
                  <p className="detail-drawer__update-body">{u.body_text}</p>
                </div>
              ))}
            </div>
            <div className="detail-drawer__note-input-wrap">
              <textarea
                className="detail-drawer__note-input"
                rows={2}
                placeholder="+ Log an update…"
                value={newNote}
                onChange={e => setNewNote(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); addUpdate(); } }}
              />
              <button className="detail-drawer__note-submit" onClick={addUpdate} disabled={!newNote.trim()}>Log</button>
            </div>
          </div>

          </div>{/* /scroll-body */}

          {/* Footer */}
          <div className="detail-drawer__zone detail-drawer__zone--footer">
            <button className="detail-drawer__btn-done" onClick={() => { onDone(task.id); onClose(); }}>
              {task.completed ? 'Mark as Active' : 'Mark as Done'}
            </button>
            <button className="detail-drawer__btn-archive" onClick={() => { onArchive(task.id); onClose(); }}>
              Move to Bucket List
            </button>
            {!deleteConfirm ? (
              <button className="detail-drawer__btn-delete"
                onClick={() => setDeleteConfirm(true)}>
                Delete
              </button>
            ) : (
              <button className="detail-drawer__btn-delete detail-drawer__btn-delete--confirm"
                onClick={() => { onDelete(task.id); onClose(); }}
                onBlur={() => setDeleteConfirm(false)}>
                Tap again to confirm
              </button>
            )}
          </div>
        </div>
      </aside>
    </>
  );
};

/* ── ActionSuggestionCard ────────────────────────────────────────────────── */

const ActionSuggestionCard = ({ suggestion, totalPending, onAdd, onDismiss, onSnooze, onClearAll }) => {
  const [dismissToastId, setDismissToastId] = React.useState(null);
  const [toastTimer,     setToastTimer]     = React.useState(null);

  if (!suggestion) return null;

  const domainColor = TASK_DOMAINS[suggestion.domain]?.color || TASK_DOMAINS.admin.color;
  const showEyebrow = totalPending > 1;
  const showClearAll = totalPending >= 3;

  const handleDismiss = () => {
    setDismissToastId(suggestion.suggestion_id);
    onDismiss(suggestion.suggestion_id, /* optimistic */ true);
    const t = setTimeout(() => {
      setDismissToastId(null);
      setToastTimer(null);
    }, 3000);
    setToastTimer(t);
  };

  const handleUndo = () => {
    if (toastTimer) clearTimeout(toastTimer);
    setDismissToastId(null);
    setToastTimer(null);
    onDismiss(suggestion.suggestion_id, /* undo */ false);
  };

  return (
    <>
      <div className="suggestion-card" role="region" aria-label="Action suggestion">
        {showEyebrow && (
          <span className="suggestion-card__eyebrow">
            {totalPending} suggestions waiting
          </span>
        )}
        <div className="suggestion-card__body">
          <span
            className="suggestion-card__dot"
            style={{background: domainColor}}
            aria-hidden="true"
          />
          <span className="suggestion-card__text">{suggestion.action_text}</span>
        </div>
        <div className="suggestion-card__actions">
          <button
            className="suggestion-card__btn suggestion-card__btn--add"
            onClick={() => onAdd(suggestion.suggestion_id)}
          >
            Add
          </button>
          <button
            className="suggestion-card__btn suggestion-card__btn--dismiss"
            onClick={handleDismiss}
          >
            Dismiss
          </button>
          <button
            className="suggestion-card__btn suggestion-card__btn--snooze"
            onClick={() => onSnooze(suggestion.suggestion_id)}
          >
            Later · 7 days
          </button>
          {showClearAll && (
            <button
              className="suggestion-card__btn suggestion-card__btn--clear"
              onClick={onClearAll}
              title="Dismiss all suggestions"
            >
              Clear all
            </button>
          )}
        </div>
      </div>
      {dismissToastId && (
        <div className="suggestion-undo-toast" role="status">
          Dismissed —{' '}
          <button className="suggestion-undo-toast__btn" onClick={handleUndo}>
            Undo
          </button>
        </div>
      )}
    </>
  );
};



/* ── TriageBanner ────────────────────────────────────────────────────────── */

const TriageBanner = ({ count, onClick }) => {
  if (!count) return null;
  return (
    <button className="triage-banner" onClick={onClick} aria-label={`${count} actions pending review`}>
      <span className="triage-banner__star" aria-hidden="true">✦</span>
      <span className="triage-banner__count">{count} action{count !== 1 ? 's' : ''} added</span>
      <span className="triage-banner__cta">Review now →</span>
    </button>
  );
};

/* ── TriageDrawer ─────────────────────────────────────────────────────────── */

const TRIAGE_HORIZONS = [
  { key: 'focus_zone',    label: 'Today'   },
  { key: 'deadline_bound', label: 'Soon'   },
  { key: 'value_aligned', label: 'Someday' },
];

const TriageCard = ({ item, onClaim, onDelete }) => {
  const [editing,     setEditing]     = useState(false);
  const [title,       setTitle]       = useState(item.ai_refined_title || '');
  const [desc,        setDesc]        = useState(item.ai_refined_description || '');
  const [domain,      setDomain]      = useState(item.domain || 'admin');
  const [pickingDate, setPickingDate] = useState(false);
  const [customDate,  setCustomDate]  = useState('');
  const claim = (horizon, dueDate = null) => {
    onClaim(item.action_id, horizon, title, dueDate, desc, domain);
  };

  const handlePickDate = () => {
    if (!pickingDate) { setPickingDate(true); return; }
    if (customDate) claim('deadline_bound', customDate);
  };

  return (
    <div className="triage-card">

      {/* Header row: domain dot + label + edit toggle */}
      <div className="triage-card__head">
        <span className="triage-card__dot" style={{ background: (TASK_DOMAINS[domain] || TASK_DOMAINS.admin).color }} />
        <span className="triage-card__domain-label">{(TASK_DOMAINS[domain] || TASK_DOMAINS.admin).label}</span>
        <button className="triage-card__edit-btn" onClick={() => setEditing(e => !e)} aria-label="Edit">
          {editing ? 'Done' : 'Edit'}
        </button>
      </div>

      {/* Edit mode: domain chips + title + description */}
      {editing ? (
        <div className="triage-card__edit">
          <div className="triage-card__domain-chips">
            {Object.entries(TASK_DOMAINS).map(([key, d]) => (
              <button key={key}
                className={"triage-card__domain-chip" + (domain === key ? " is-active" : "")}
                style={domain === key ? {borderColor: d.color, color: d.color} : {}}
                onClick={() => setDomain(key)}
              >
                <span style={{background: d.color}} className="triage-card__domain-chip-dot" />
                {d.label}
              </button>
            ))}
          </div>
          <input
            className="triage-card__input"
            value={title}
            onChange={e => setTitle(e.target.value)}
            placeholder="Title"
          />
          <textarea
            className="triage-card__textarea"
            value={desc}
            onChange={e => setDesc(e.target.value)}
            placeholder="Description (optional)"
            rows={2}
          />
        </div>
      ) : (
        <p className="triage-card__title">{title}</p>
      )}

      {/* Source context — same component as Task Details drawer */}
      <SegmentContext actionId={item.action_id} />

      {/* Placement row */}
      <div className="triage-card__actions">
        <div className="triage-card__placements">
          <button className="triage-card__place" onClick={() => claim('focus_zone', _fmt(_today()))}>Today</button>
          <button className="triage-card__place" onClick={() => claim('deadline_bound', _fmt(_inSevenDays()))}>Soon</button>
          <button className="triage-card__place" onClick={() => claim('value_aligned', null)}>Someday</button>
          <button
            className={"triage-card__place triage-card__place--date" + (pickingDate ? " is-picking" : "")}
            onClick={handlePickDate}
          >
            {pickingDate ? 'Confirm →' : 'Pick a date'}
          </button>
        </div>
        <button className="triage-card__trash" onClick={() => onDelete(item.action_id)} aria-label="Delete">
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="3 6 5 6 21 6"/>
            <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
            <path d="M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
          </svg>
        </button>
      </div>

      {/* Inline date input (only when "Pick a date" is active) */}
      {pickingDate && (
        <div className="triage-card__date-input-row">
          <input
            type="date"
            className="triage-card__date-input"
            value={customDate}
            onChange={e => setCustomDate(e.target.value)}
            min={_fmt(_today())}
            autoFocus
          />
          <button className="triage-card__date-cancel" onClick={() => { setPickingDate(false); setCustomDate(''); }}>Cancel</button>
        </div>
      )}
    </div>
  );
}

const TriageDrawer = ({ items, onClose, onClaim, onDelete, onDeleteAll }) => {
  const [confirmDelete, setConfirmDelete] = useState(false);

  // Group by date (YYYY-MM-DD from created_at)
  const grouped = items.reduce((acc, item) => {
    const day = (item.created_at || '').slice(0, 10) || 'Unknown';
    if (!acc[day]) acc[day] = [];
    acc[day].push(item);
    return acc;
  }, {});
  const days = Object.keys(grouped).sort().reverse();

  const fmt = (iso) => {
    try {
      return new Date(iso + 'T00:00:00').toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' });
    } catch { return iso; }
  };

  return (
    <div className="triage-overlay" role="dialog" aria-modal="true" aria-label="Review incoming actions">
      <div className="triage-drawer">
        <div className="triage-drawer__head">
          <span className="label-caps triage-drawer__eye">Incoming</span>
          <span className="triage-drawer__count">✦ {items.length} to review</span>
          <button className="triage-drawer__close" onClick={onClose} aria-label="Close">×</button>
        </div>

        <div className="triage-drawer__body">
          {days.map(day => (
            <div key={day} className="triage-drawer__group">
              <div className="triage-drawer__date-sep">
                <span className="triage-drawer__date-label">{fmt(day)}</span>
              </div>
              {grouped[day].map(item => (
                <TriageCard
                  key={item.action_id}
                  item={item}
                  onClaim={onClaim}
                  onDelete={onDelete}
                />
              ))}
            </div>
          ))}
        </div>

        <div className="triage-drawer__foot">
          {confirmDelete ? (
            <div className="triage-drawer__confirm">
              <span className="triage-drawer__confirm-msg">Delete all {items.length} items?</span>
              <button className="triage-drawer__confirm-yes" onClick={() => { onDeleteAll(); setConfirmDelete(false); }}>Yes, delete all</button>
              <button className="triage-drawer__confirm-no" onClick={() => setConfirmDelete(false)}>Cancel</button>
            </div>
          ) : (
            <button className="triage-drawer__delete-all" onClick={() => setConfirmDelete(true)}>Delete all</button>
          )}
        </div>
      </div>
    </div>
  );
};

/* ── UndoToast ────────────────────────────────────────────────────────────── */

const UndoToast = ({ message, onUndo, onDismiss }) => (
  <div className="undo-toast" role="status">
    <span className="undo-toast__msg">{message}</span>
    <button className="undo-toast__undo" onClick={onUndo}>Undo</button>
    <button className="undo-toast__close" onClick={onDismiss} aria-label="Dismiss">×</button>
  </div>
);

/* ── ValueDrawer — evidence trail for a Value Velocity value ──────────────── */

const wpText = (value) => String(value || '').replace(/\s+/g, ' ').trim();
const wpQuote = (value, max = 120) => {
  const text = wpText(value);
  if (!text) return '';
  if (text.length <= max) return text;
  return text.slice(0, max).replace(/\s+\S*$/, '') + '...';
};

const WP_EVIDENCE_LABELS = {
  repeated_behavior: 'Repeated behavior',
  action: 'Action',
  segment: 'Segment',
  tension: 'Tension note',
};

const fmtAge = (h) => {
  const v = Number(h || 0);
  if (v <= 6) return 'fresh today';
  if (v < 48) return `${Math.round(v)}h old`;
  const d = Math.round(v / 24);
  return d < 14 ? `${d}d old` : `${Math.round(d / 7)}w old`;
};

const groupWaypointEvidence = (waypoint) => {
  const raw = Array.isArray(waypoint.evidence) ? waypoint.evidence : [];
  const evidence = raw.map((item, index) => ({
    id: item.id || `${waypoint.id}-evidence-${index}`,
    type: item.type || 'segment',
    label: WP_EVIDENCE_LABELS[item.type] || 'Evidence',
    excerpt: wpQuote(item.excerpt || item.text || item.detail || '', 150),
    sourceDate: item.source_date || item.date || '',
    dissonanceNote: item.dissonance_note || '',
  })).filter(item => item.excerpt || item.dissonanceNote);

  const fallbackSegments = !evidence.length
    ? (waypoint.supportingSegments || []).map((seg, index) => ({
        id: seg.id || `${waypoint.id}-segment-${index}`,
        type: 'segment',
        label: 'Segment',
        excerpt: wpQuote(seg.fullText || seg.text, 150),
        sourceDate: seg.date || '',
        dissonanceNote: '',
      })).filter(item => item.excerpt)
    : [];

  const merged = evidence.length ? evidence : fallbackSegments;
  return {
    repeated: merged.filter(item => item.type === 'repeated_behavior'),
    actions: merged.filter(item => item.type === 'action'),
    segments: merged.filter(item => item.type === 'segment'),
    tension: merged.filter(item => item.type === 'tension' || item.dissonanceNote),
  };
};

const WaypointEvidenceList = ({ title, items, emptyText }) => (
  <section className="wp-drawer__section">
    <div className="wp-drawer__section-head">
      <span className="label-caps">{title}</span>
      <span className="wp-drawer__count">{items.length}</span>
    </div>
    {items.length > 0 ? (
      <div className="wp-drawer__items">
        {items.map(item => (
          <article key={item.id} className="wp-drawer__item">
            <div className="wp-drawer__item-top">
              <strong className="wp-drawer__item-label">{item.label}</strong>
              {item.sourceDate ? <span className="wp-drawer__item-meta">{item.sourceDate}</span> : null}
            </div>
            {item.excerpt && <p className="wp-drawer__item-detail">{item.excerpt}</p>}
            {item.dissonanceNote && <p className="wp-drawer__item-detail">{item.dissonanceNote}</p>}
          </article>
        ))}
      </div>
    ) : (
      <p className="wp-drawer__empty">{emptyText}</p>
    )}
  </section>
);

const WaypointDrawer = ({ waypoint, onClose }) => {
  const [reflection, setReflection] = useState('');

  useEffect(() => {
    if (!waypoint) return;
    setReflection('');
  }, [waypoint && waypoint.id]);

  useEffect(() => {
    if (!waypoint) return;
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => {
      document.body.style.overflow = prevOverflow;
      window.removeEventListener('keydown', onKey);
    };
  }, [waypoint, onClose]);

  if (!waypoint) return null;

  const grouped = groupWaypointEvidence(waypoint);
  const summary = waypoint.evidenceSummary || {};
  const staleLabel = fmtAge(waypoint.staleHours);

  return (
    <div className="wp-modal wp-modal--drawer" onClick={onClose}>
      <aside
        className="wp-modal__panel wp-modal__panel--drawer"
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-label={`${waypoint.valueGoal || 'Waypoint'} evidence drawer`}
      >
        <button className="wp-drawer__close" onClick={onClose} aria-label="Close drawer">x</button>

        <header className="wp-drawer__head">
          <p className="label-caps wp-drawer__kicker">Reflection detail</p>
          <h2 className="wp-drawer__title">{waypoint.headline || waypoint.valueGoal}</h2>
          <p className="wp-drawer__value">{waypoint.valueGoal}</p>
          {waypoint.description && <p className="wp-drawer__description">{waypoint.description}</p>}
          <div className="wp-drawer__meta">
            <span>{waypoint.domain || 'Waypoint'}</span>
            <span>{summary.segment_count || 0} segments</span>
            <span>{summary.repeated_behavior_count || 0} repeated behavior</span>
            <span>{summary.action_count || 0} actions</span>
            <span>{staleLabel}</span>
          </div>
        </header>

        <div className="wp-drawer__body">
          <section className="wp-drawer__section wp-drawer__reflect">
            <label className="label-caps" htmlFor="waypoint-reflection">What made this possible?</label>
            <textarea
              id="waypoint-reflection"
              className="wp-drawer__textarea"
              value={reflection}
              onChange={(e) => setReflection(e.target.value)}
              rows={4}
            />
            <div className="wp-drawer__actions">
              <button className="wp-drawer__primary" type="button" onClick={onClose}>Save waypoint</button>
              <button className="wp-drawer__link" type="button">adjust meaning</button>
            </div>
          </section>

          <WaypointEvidenceList
            title="Repeated behavior"
            items={grouped.repeated}
            emptyText="No repeated behavior evidence was attached."
          />
          <WaypointEvidenceList
            title="Actions"
            items={grouped.actions}
            emptyText="No action evidence was attached."
          />
          <WaypointEvidenceList
            title="Segments"
            items={grouped.segments}
            emptyText="No source segments were attached to this waypoint."
          />

          <details className="wp-drawer__section wp-drawer__optional">
            <summary className="wp-drawer__optional-summary">
              <span className="label-caps">Show optional notes</span>
              <span className="wp-drawer__count">{grouped.tension.length}</span>
            </summary>
            {grouped.tension.length > 0 ? (
              <div className="wp-drawer__items">
                {grouped.tension.map(item => (
                  <article key={item.id} className="wp-drawer__item">
                    <div className="wp-drawer__item-top">
                      <strong className="wp-drawer__item-label">Tension note</strong>
                      {item.sourceDate ? <span className="wp-drawer__item-meta">{item.sourceDate}</span> : null}
                    </div>
                    <p className="wp-drawer__item-detail">{item.dissonanceNote || item.excerpt}</p>
                  </article>
                ))}
              </div>
            ) : (
              <p className="wp-drawer__empty">No optional notes were attached.</p>
            )}
          </details>
        </div>
      </aside>
    </div>
  );
};

const ValueDrawer = ({ item, onClose }) => {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(`/api/values/${item.valueGoalId}/evidence`)
      .then(r => r.ok ? r.json() : null)
      .then(setData)
      .catch(() => {});
  }, [item.valueGoalId]);
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  });
  const meta = TASK_DOMAINS[item.domain] || TASK_DOMAINS.admin;
  return (
    <div className="value-drawer-veil" onClick={onClose}>
      <aside className="value-drawer" onClick={e => e.stopPropagation()} role="dialog" aria-modal="true" aria-label={`${item.label} evidence`}>
        <button className="value-drawer__close" onClick={onClose} aria-label="Close">✕</button>
        <header className="value-drawer__head">
          <span className="value-drawer__eye" style={{ color: meta.color }}>{meta.label}</span>
          <h2 className="value-drawer__title">{(data && data.name) || item.label}</h2>
          <div className="value-drawer__metrics">
            <span className="value-drawer__pct">
              {item.count} events
              {(item.trend === "up_strong" || item.trend === "up") && <span className="vd-trend is-up"> ▲</span>}
              {item.trend === "down" && <span className="vd-trend is-down"> ▼</span>}
            </span>
          </div>
          {data && data.weekly_counts && (() => {
            const wk = data.weekly_counts;
            const maxC = Math.max(...wk.map(w => w.count), 1);
            return (
              <div className="value-drawer__sparkline" aria-label="8-week activity trend">
                <svg width="96" height="32" aria-hidden="true">
                  {wk.map((w, i) => {
                    const h = Math.max(Math.round((w.count / maxC) * 26), w.count > 0 ? 2 : 0);
                    const isCur = i === 7;
                    return (
                      <rect key={i} x={i * 12} y={30 - h} width={10} height={h || 2}
                        fill={isCur && w.count === 0 ? "transparent" : meta.color}
                        opacity={w.count === 0 ? 0.12 : isCur ? 1 : 0.4}
                        rx="1.5" />
                    );
                  })}
                  <line x1="0" y1="31" x2="96" y2="31" stroke="currentColor" strokeOpacity="0.08" strokeWidth="1" />
                </svg>
                <span className="value-drawer__spark-label">8 weeks</span>
              </div>
            );
          })()}
          {data && data.description && <p className="value-drawer__desc">{data.description}</p>}
        </header>
        <div className="value-drawer__body">
          <span className="label-caps value-drawer__sec">Evidence Trail</span>
          {data === null && <p className="dash2-empty">Loading…</p>}
          {data && data.evidence.length === 0 && <p className="dash2-empty">No evidence logged yet.</p>}
          {data && data.evidence.map((ev, i) => {
            const em = TASK_DOMAINS[ev.domain] || TASK_DOMAINS.admin;
            return (
              <article key={i} className={"value-ev" + (ev.type === "practice" ? " value-ev--practice" : "")}>
                {ev.type === "practice" && (
                  <div className="value-ev__practice-badge">
                    <span className="value-ev__practice-icon" aria-hidden="true">&#9670;</span>
                    <span>PRACTICE</span>
                  </div>
                )}
                {ev.justification && <p className="value-ev__just">{ev.justification}</p>}
                {ev.snippet && <blockquote className="value-ev__quote">"{ev.snippet}"</blockquote>}
                <div className="value-ev__foot">
                  <span style={{ color: em.color }}>{em.label}</span>
                  <span>{ev.date ? String(ev.date).slice(0, 10) : ""}</span>
                  {ev.type === "practice"
                    ? <span className="value-ev__weight">weight {ev.weight}</span>
                    : <span>conf {Math.round((ev.confidence || 0) * 100)}%</span>}
                </div>
              </article>
            );
          })}
        </div>
      </aside>
    </div>
  );
};

/* ── TriageStepper — focused one-at-a-time review popup ──────────────────── */

const TriageStepper = ({ items, startIndex, onClaim, onDelete, onClose }) => {
  const [idx, setIdx] = useState(startIndex || 0);
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  });
  useEffect(() => { if (!items.length) onClose(); }, [items.length]);
  if (!items.length) return null;
  const safe = Math.min(idx, items.length - 1);
  const item = items[safe];
  return (
    <div className="triage-overlay" role="dialog" aria-modal="true" aria-label="Review incoming" onClick={onClose}>
      <div className="triage-stepper" onClick={e => e.stopPropagation()}>
        <div className="triage-stepper__head">
          <span className="label-caps triage-stepper__count">Review · {safe + 1} of {items.length}</span>
          <button className="triage-stepper__close" onClick={onClose} aria-label="Close">×</button>
        </div>
        <div className="triage-stepper__body">
          <TriageCard key={item.action_id} item={item} onClaim={onClaim} onDelete={onDelete} />
        </div>
        <div className="triage-stepper__nav">
          <button type="button" className="triage-stepper__prev"
            disabled={safe === 0} onClick={() => setIdx(i => Math.max(0, i - 1))}>‹ Prev</button>
          <span className="triage-stepper__hint">Pick a lane above</span>
          <button type="button" className="triage-stepper__next"
            disabled={safe >= items.length - 1} onClick={() => setIdx(i => Math.min(items.length - 1, i + 1))}>Next ›</button>
        </div>
      </div>
    </div>
  );
};

/* ── AcceptanceColumn — incoming triage as the first action column ───────── */

const AcceptanceColumn = ({ def, items, onClaim, onDelete, onOpen,
                           isExpanded, isCollapsed, onToggleExpand }) => {
  const isEmpty = items.length === 0;
  return (
    <section
      className={"col col--acceptance" + (isExpanded ? " is-expanded" : "") + (isCollapsed ? " is-collapsed" : "")}
      aria-label={def.title}>
      <header className="col__head">
        <div className="col__title-row">
          <h3 className="col__title column-title">{def.title} ({items.length})</h3>
          <div className="col__head-controls">
            {onToggleExpand && (
              <button className={"col__expand-btn" + (isExpanded ? " is-on" : "")}
                onClick={() => onToggleExpand(def.id)} aria-expanded={!!isExpanded}
                title={isExpanded ? `Collapse ${def.title}` : `Expand ${def.title}`}>
                {isExpanded ? (
                  <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor"
                    strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                    <path d="M6 2v4H2M10 14v-4h4M6 6 2 2M10 10l4 4" />
                  </svg>
                ) : (
                  <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor"
                    strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                    <path d="M9 2h5v5M14 2 9 7M7 14H2V9M2 14l5-5" />
                  </svg>
                )}
              </button>
            )}
          </div>
        </div>
        <span className="col__sub"><em>{def.sub}</em></span>
      </header>
      <div className="col__list col__list--acceptance" role="list">
        {isEmpty && <div className="col__empty">{def.empty}</div>}
        {items.slice(0, 3).map((item, i) => {
          const dm = TASK_DOMAINS[item.domain] || TASK_DOMAINS.admin;
          return (
            <div key={item.action_id} className="accept-card" role="button" tabIndex={0}
              draggable
              onDragStart={e => { e.dataTransfer.setData('text/plain', 'triage:' + item.action_id); e.dataTransfer.effectAllowed = 'move'; }}
              title="Drag to a lane, or click to pick"
              onClick={() => onOpen(i)}
              onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(i); } }}
              style={{ '--accent': dm.color }}>
              <span className="accept-card__grip" aria-hidden="true">⠿</span>
              <span className="accept-card__title">{item.ai_refined_title}</span>
              <button type="button" className="accept-card__dismiss" aria-label="Dismiss"
                onClick={e => { e.stopPropagation(); onDelete(item.action_id); }}>✕</button>
            </div>
          );
        })}
      </div>
      {items.length > 0 && (
        <button type="button" className="col-acceptance__review" onClick={() => onOpen(0)}>
          View all ({items.length}){items.length > 3 ? ` · ${items.length - 3} more` : ''} →
        </button>
      )}
    </section>
  );
};

/* ── Momentum Log — fixed-geometry rhythm rows w/ rank + 7-day grid ──────── */

const MOMENTUM_PINNED = 3;

const MomentumRowMenu = ({ rank, total, onMoveToRank, disabled }) => {
  const [open, setOpen] = useState(false);
  useEffect(() => {
    if (!open) return;
    const close = () => setOpen(false);
    window.addEventListener('click', close);
    return () => window.removeEventListener('click', close);
  }, [open]);
  return (
    <div className="mlog-menu">
      <button className="mlog-menu__btn" disabled={disabled} aria-haspopup="menu" aria-expanded={open}
        title="Reorder this practice" aria-label={`Reorder, currently rank ${rank}`}
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}>⋯</button>
      {open && (
        <div className="mlog-menu__pop" role="menu" onClick={e => e.stopPropagation()}>
          {Array.from({ length: total }, (_, i) => i + 1).map(r => (
            <button key={r} role="menuitem" className={"mlog-menu__item" + (r === rank ? " is-current" : "")}
              disabled={r === rank} onClick={() => { setOpen(false); onMoveToRank(r); }}>
              Move to rank {r}{r === rank ? ' (current)' : ''}
            </button>
          ))}
        </div>
      )}
    </div>
  );
};

const MOMENTUM_WD = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
const momentumWdLetter = (d) => MOMENTUM_WD[new Date(d + 'T00:00:00').getDay()];

const AddPracticeForm = ({ onAdd, onCancel }) => {
  const [name, setName] = useState('');
  const [domain, setDomain] = useState('inner');
  const submit = () => { if (name.trim()) onAdd(name.trim(), domain); };
  return (
    <div className="mlog-add">
      <input className="mlog-add__name" placeholder="New practice name" value={name} maxLength={60} autoFocus
        onChange={e => setName(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter') submit(); if (e.key === 'Escape') onCancel(); }} />
      <div className="mlog-add__domains" role="radiogroup" aria-label="Domain">
        {Object.entries(TASK_DOMAINS).filter(([k]) => k !== 'aligned').map(([k, m]) => (
          <button key={k} type="button" title={m.label} aria-label={m.label} aria-pressed={domain === k}
            className={"mlog-add__dom" + (domain === k ? " is-active" : "")}
            style={domain === k ? { borderColor: m.color } : undefined}
            onClick={() => setDomain(k)}>
            <span className="mlog-add__dot" style={{ background: m.color }} />
          </button>
        ))}
      </div>
      <button type="button" className="mlog-add__go" onClick={submit} disabled={!name.trim()}>Add</button>
      <button type="button" className="mlog-add__x" onClick={onCancel} aria-label="Cancel">✕</button>
    </div>
  );
};

const MomentumLog = ({ practices, rhythm, days, expanded, onToggleExpand, onReorder, onCellClick, onAddPractice, busy }) => {
  const dragIndex = useRef(null);
  const [showAdd, setShowAdd] = useState(false);
  const pastDays = days.slice(0, 6);
  const handleAdd = (n, d) => { onAddPractice(n, d); setShowAdd(false); };

  const header = (
    <div className="momentum-log__head">
      <span className="momentum-log__eye">Momentum Log</span>
      <span className="momentum-log__hint">Last 7 days · drag or ⋯ to reorder</span>
      <button className="momentum-log__add" onClick={() => setShowAdd(s => !s)} aria-expanded={showAdd}>+ Add</button>
      {practices.length > MOMENTUM_PINNED && (
        <button className="momentum-log__toggle" onClick={onToggleExpand} aria-expanded={expanded}>
          {expanded ? '↑ Collapse' : `↓ Expand (${practices.length})`}
        </button>
      )}
    </div>
  );

  if (!practices.length) {
    return (
      <section className="momentum-log" aria-label="Momentum Log">
        {header}
        {showAdd && <AddPracticeForm onAdd={handleAdd} onCancel={() => setShowAdd(false)} />}
        <p className="momentum-log__empty">No practices plotted. Add your first rhythm.</p>
      </section>
    );
  }

  const shown = expanded ? practices : practices.slice(0, MOMENTUM_PINNED);
  const ids = practices.map(p => p.id);
  const moveToRank = (pid, rank) => { const rest = ids.filter(x => x !== pid); rest.splice(rank - 1, 0, pid); onReorder(rest); };
  const onDrop = (toIdx) => {
    const from = dragIndex.current; dragIndex.current = null;
    if (from === null || from === toIdx) return;
    const next = [...ids]; const [moved] = next.splice(from, 1); next.splice(toIdx, 0, moved); onReorder(next);
  };

  return (
    <section className={"momentum-log" + (busy ? " is-busy" : "")} aria-label="Momentum Log" aria-busy={busy}>
      {header}
      {showAdd && <AddPracticeForm onAdd={handleAdd} onCancel={() => setShowAdd(false)} />}
      <div className="mlog-colhead">
        <span className="mlog-colhead__lead" />
        <div className="mlog-track mlog-track--head">
          <span className="mlog-leader" />
          {pastDays.map((d, i) => <span key={i} className="mlog-collbl">{momentumWdLetter(d)}</span>)}
          <span className="mlog-collbl mlog-collbl--today">Today</span>
        </div>
      </div>
      <div className={"momentum-log__rows" + (expanded ? " is-expanded" : "")}>
        {shown.map((pr, idx) => {
          const m = TASK_DOMAINS[pr.domain] || TASK_DOMAINS.admin;
          const cells = rhythm[pr.id] || [];
          const todayDone = !!cells[6];
          return (
            <div key={pr.id} className="mlog-row" draggable={!busy}
              onDragStart={() => { dragIndex.current = idx; }}
              onDragOver={e => e.preventDefault()}
              onDrop={() => onDrop(idx)}>
              <span className="mlog-row__grip" aria-hidden="true" title="Drag to reorder">≡</span>
              <span className="mlog-row__glyph" aria-hidden="true"
                style={{ "--ico": `url(/dashboard-ui/assets/icons/${m.ico}.svg)`, background: m.color }} />
              <span className="mlog-row__name" title={pr.name}>{pr.name}</span>
              <div className="mlog-track" role="img" aria-label={`${pr.name}: ${cells.filter(Boolean).length} of 7 days`}>
                <span className="mlog-leader" aria-hidden="true" />
                {pastDays.map((d, i) => (
                  <span key={i} className="mlog-slot">
                    <button type="button"
                      className={"mlog-dot" + (cells[i] ? " is-done" : "")}
                      style={cells[i] ? { background: m.color } : undefined}
                      title={d + (cells[i] ? ' · logged — open' : ' · click to log')}
                      aria-label={`${pr.name} ${d}${cells[i] ? ' logged' : ''}`}
                      onClick={() => onCellClick(pr.id, d, !!cells[i])} disabled={busy}>
                      {!cells[i] && <span className="mlog-dot__plus" aria-hidden="true">+</span>}
                    </button>
                  </span>
                ))}
                <span className="mlog-slot mlog-slot--today">
                  <button type="button"
                    className={"mlog-dot mlog-dot--today" + (todayDone ? " is-done" : "")}
                    style={todayDone ? { background: m.color } : undefined}
                    title={todayDone ? 'Logged today — open' : 'Click to log today'}
                    aria-label={`${pr.name} today`}
                    onClick={() => onCellClick(pr.id, days[6], todayDone)} disabled={busy}>
                    {!todayDone && <span className="mlog-dot__plus" aria-hidden="true">+</span>}
                  </button>
                  <MomentumRowMenu rank={idx + 1} total={practices.length} disabled={busy}
                    onMoveToRank={(r) => moveToRank(pr.id, r)} />
                </span>
              </div>
            </div>
          );
        })}
      </div>
    </section>
  );
};

/* ── ActionStream root (Module 7) ────────────────────────────────────────── */

const ActionStream = () => {
  const [tasks,        setTasks]        = useState([]);
  const [liveMsg,      setLiveMsg]      = useState('');
  const [draggingId,   setDraggingId]   = useState(null);
  const [detailTask,   setDetailTask]   = useState(null);
  const [dragOverCol,  setDragOverCol]  = useState(null);
  const [dragOverSlot, setDragOverSlot] = useState(null);
  const [realizingId,  setRealizingId]  = useState(null);
  const [dismissingId, setDismissingId] = useState(null);
  const [captureFor,   setCaptureFor]   = useState(null);
  const [captureType,  setCaptureType]  = useState(null);
  const [domainFilter, setDomainFilter] = useState([]);
  const [showTriage,   setShowTriage]   = useState(false);
  const [triageItems,  setTriageItems]  = useState([]);
  const [suggestion,        setSuggestion]        = useState(null);
  const [totalPending,      setTotalPending]       = useState(0);
  const [toast,        setToast]        = useState(null); // {message, action_ids}
  const [pendingDate,  setPendingDate]  = useState(null); // {id} awaiting due_date after drop into deadline-bound
  const toastTimerRef = useRef(null);
  const [momentumRegistry, setMomentumRegistry] = useState([]);
  const [momentumLog, setMomentumLog] = useState(null);
  const [momentumExpanded, setMomentumExpanded] = useState(false);
  const [momentumBusy, setMomentumBusy] = useState(false);
  const [dayDetail, setDayDetail] = useState(null);
  const [alignmentDomains, setAlignmentDomains] = useState([]);
  const [valueVelocity, setValueVelocity] = useState(null);
  const [ledger, setLedger] = useState(null);
  const [expandedValues, setExpandedValues] = useState(() => new Set());
  const [valueDetail, setValueDetail] = useState(null);
  const [waypointDetail, setWaypointDetail] = useState(null);
  const [triageStep, setTriageStep] = useState(null);
  const [waypointSummaries, setWaypointSummaries] = useState([]);
  const [waypointPage, setWaypointPage] = useState(0);
  const [liveReview, setLiveReview] = useState(null);
  const [expandedLane, setExpandedLane] = useState('focus_zone');

  // Per-column sort state — persisted in localStorage
  const loadSorts = () => {
    try { return JSON.parse(localStorage.getItem('colSorts') || '{}'); } catch { return {}; }
  };
  const [colSorts, setColSorts] = useState(loadSorts);
  const setSortFor = (colId, key) => {
    setColSorts(prev => {
      const next = {...prev, [colId]: key};
      try { localStorage.setItem('colSorts', JSON.stringify(next)); } catch {}
      return next;
    });
  };

  // Sort helper
  const applySortToCol = (colTasks, colId) => {
    const key = colSorts[colId] || 'manual';
    if (key === 'alignment') return [...colTasks].sort((a,b) => (b.score||0) - (a.score||0));
    if (key === 'oldest')    return [...colTasks].sort((a,b) => (a._createdAt||'').localeCompare(b._createdAt||''));
    return colTasks; // manual
  };

  // Load from /api/actions/dashboard on mount
  useEffect(() => {
    fetch('/api/actions/dashboard')
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        const loaded = [];
        Object.entries(d).forEach(([horizon, actions]) => {
          const col = HORIZON_TO_COL[horizon] || 'focus_zone';
          (actions || []).forEach(a => {
            loaded.push({
              id:           a.action_id,
              column:       col,
              domain:       a.domain  || 'admin',
              title:        a.ai_refined_title,
              body:         a.ai_refined_description || '',
              segment:      a.origin_snippet || '',
              score:        a.score          || 0.5,
              completed:    a.lifecycle_status === 'done',
              due:          a.due_date        || null,
              _createdAt:   a.created_at      || '',
              staleDays:    0,
              folder:       undefined,
            });
          });
        });
        setTasks(loaded);
      })
      .catch(() => {});
  }, []);

  // Triage: fetch pending_review items on mount
  const fetchTriageItems = React.useCallback(() => {
    fetch('/api/actions/pending-review')
      .then(r => r.ok ? r.json() : [])
      .then(setTriageItems)
      .catch(() => {});
  }, []);

  useEffect(() => { fetchTriageItems(); }, [fetchTriageItems]);

  useEffect(() => {
    fetch('/api/navigation/map')
      .then(r => r.ok ? r.json() : [])
      .then(setAlignmentDomains)
      .catch(() => {});
  }, []);

  useEffect(() => {
    fetch('/api/values/velocity')
      .then(r => r.ok ? r.json() : null)
      .then(setValueVelocity)
      .catch(() => {});
  }, []);

  const refetchLedger = () =>
    fetch('/api/dashboard/ledger').then(r => r.ok ? r.json() : null).then(setLedger).catch(() => {});

  useEffect(() => { refetchLedger(); }, []);

  const logPracticeDay = (practiceId, date) => {
    fetch('/api/momentum/log/toggle', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ practice_id: practiceId, date: date }),
    }).then(r => { if (!r.ok) throw new Error('toggle failed'); })
      .then(refetchLedger).catch(refetchLedger);
  };

  const toggleValueExpand = (id) => setExpandedValues(s => {
    const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n;
  });

  // Suggestion card: fetch on mount and after each decision
  const fetchSuggestion = React.useCallback(() => {
    fetch('/api/action-suggestions')
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        setSuggestion(d.suggestion || null);
        setTotalPending(d.total_pending || 0);
      })
      .catch(() => {});
  }, []);

  useEffect(() => { fetchSuggestion(); }, []);

  useEffect(() => {
    fetch('/api/momentum/registry')
      .then(r => r.ok ? r.json() : [])
      .then(setMomentumRegistry)
      .catch(() => {});
    fetch('/api/momentum/log?days=7')
      .then(r => r.ok ? r.json() : null)
      .then(setMomentumLog)
      .catch(() => {});
    fetch('/api/waypoints/today?limit=8')
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        setWaypointSummaries((d.waypoints || []).slice(0, 1));
      })
      .catch(() => {});
    fetch('/api/v1/review?limit=3')
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        setLiveReview(d);
      })
      .catch(() => {});
  }, []);

  // Keyboard shortcuts
  useEffect(() => {
    const onKey = (e) => {
      if (captureFor || detailTask) return;
      const t = e.target;
      const inField = t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable);
      if (inField) return;
      if ((e.key === 'k' || e.key === 'K') && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setCaptureFor('any'); }
      else if ((e.key === 'n' || e.key === 'N') && !e.metaKey && !e.ctrlKey) { e.preventDefault(); setCaptureFor('any'); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [captureFor, detailTask]);

  // Header events
  useEffect(() => {
    const onFilter  = (e) => { setDomainFilter(Array.isArray(e.detail.domains) ? e.detail.domains : (e.detail.domain ? [e.detail.domain] : [])); };
    const onRefresh = () => {
      fetch('/api/actions/dashboard')
        .then(r => r.ok ? r.json() : null)
        .then(d => {
          if (!d) return;
          const loaded = [];
          Object.entries(d).forEach(([horizon, actions]) => {
            const col = HORIZON_TO_COL[horizon] || 'focus_zone';
            (actions || []).forEach(a => loaded.push({
              id: a.action_id, column: col, domain: a.domain || 'admin',
              title: a.ai_refined_title, body: a.ai_refined_description || '',
              segment: a.origin_snippet || '', score: a.score || 0.5,
              completed: a.lifecycle_status === 'done', due: a.due_date || null,
              _createdAt: a.created_at || '', staleDays: 0, folder: undefined,
            }));
          });
          setTasks(loaded);
        }).catch(() => {});
    };
    const onCapture = (e) => { setCaptureType(e.detail.type || null); setCaptureFor('any'); };
    window.addEventListener('soulitics:open-capture', onCapture);
    window.addEventListener('soulitics:domain-filter', onFilter);
    window.addEventListener('soulitics:board-refresh', onRefresh);
    return () => {
      window.removeEventListener('soulitics:open-capture', onCapture);
      window.removeEventListener('soulitics:domain-filter', onFilter);
      window.removeEventListener('soulitics:board-refresh', onRefresh);
    };
  }, []);

  const tasksByCol = (col) => {
    const base = tasks.filter(t => t.column === col && (!domainFilter.length || domainFilter.includes(t.domain)));
    return applySortToCol(base, col);
  };
  const handleToggleLane = (laneId) => {
    setExpandedLane(prev => (prev === laneId ? 'focus_zone' : laneId));
  };
  const registryPreview = momentumRegistry.slice(0, 4);
  const waypointCount = waypointSummaries.length;
  const currentWaypoint = waypointCount ? waypointSummaries[0] : null;
  const waypointDisplayCount = currentWaypoint ? 1 : 0;
  const normalizeWaypointName = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();

  // 7-day rhythm: last 7 dates (oldest→newest) + per-practice done flags from the log.
  const momentumDays = React.useMemo(() => {
    const out = [];
    const d = new Date();
    for (let i = 6; i >= 0; i--) {
      const t = new Date(d); t.setDate(d.getDate() - i);
      out.push(`${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(t.getDate()).padStart(2, '0')}`);
    }
    return out;
  }, []);
  const momentumRhythm = React.useMemo(() => {
    const entries = (momentumLog && momentumLog.entries) || {};
    const doneByDate = {};
    for (const date of Object.keys(entries)) {
      doneByDate[date] = new Set((entries[date] || []).map(e => e.practice_id));
    }
    const map = {};
    for (const pr of momentumRegistry) {
      map[pr.id] = momentumDays.map(date => !!(doneByDate[date] && doneByDate[date].has(pr.id)));
    }
    return map;
  }, [momentumLog, momentumRegistry, momentumDays]);

  const handleMomentumReorder = (orderedIds) => {
    setMomentumBusy(true);
    const prev = momentumRegistry;
    const byId = Object.fromEntries(prev.map(p => [p.id, p]));
    const next = orderedIds.map(id => byId[id]).filter(Boolean);
    setMomentumRegistry(next);
    fetch('/api/practices/sort', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ order: orderedIds }),
    })
      .then(r => { if (!r.ok) throw new Error('sort failed'); })
      .catch(() => {
        setMomentumRegistry(prev); // rollback
        fetch('/api/momentum/registry').then(r => r.ok ? r.json() : prev).then(setMomentumRegistry).catch(() => {});
      })
      .finally(() => setMomentumBusy(false));
  };

  const refetchMomentumLog = () =>
    fetch('/api/momentum/log?days=7').then(r => r.ok ? r.json() : null).then(setMomentumLog).catch(() => {});

  // Click a day cell: if empty, log it for that day (the gold +); then open the detail drawer.
  const handleCellClick = (pid, date, done) => {
    if (!done) {
      setMomentumLog(prev => {
        const entries = { ...((prev && prev.entries) || {}) };
        const list = (entries[date] || []).slice();
        if (!list.some(e => e.practice_id === pid)) list.push({ practice_id: pid, count: 1 });
        entries[date] = list;
        return { ...(prev || {}), entries };
      });
      fetch('/api/momentum/log', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ practice_id: pid, date }),
      }).then(r => { if (!r.ok) throw new Error('log failed'); }).catch(refetchMomentumLog);
    }
    setDayDetail({ practiceId: pid, entryDate: date });
  };

  const handleAddPractice = (name, domain) => {
    const icon = (TASK_DOMAINS[domain] || TASK_DOMAINS.admin).ico;
    fetch('/api/momentum/registry', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, domain, icon }),
    })
      .then(r => r.ok ? r.json() : null)
      .then(() => fetch('/api/momentum/registry').then(r => r.ok ? r.json() : []).then(setMomentumRegistry))
      .catch(() => {});
  };
  const liveReviewMeta = liveReview?.report?._metadata || {};
  const liveReviewSnippet = liveReview?.fixture?.entries?.[0]?.text || '';
  const alignmentValues = alignmentDomains.flatMap(d => d.values || []);
  const liveVelocity = valueVelocity && Array.isArray(valueVelocity.values) ? valueVelocity.values : [];
  const valueVelocityRows = liveVelocity.length
    ? liveVelocity.map(v => ({
        label: (v.name || 'Value').toUpperCase(),
        percent: clampPct(v.percent),
        count: v.count || 0,
        todayCount: v.today_count || 0,
        domain: v.primary_domain || v.domain || 'admin',
        primaryDomain: v.primary_domain || null,
        trend: v.trend || 'flat',
        valueGoalId: v.value_goal_id,
      }))
    : renderValueVelocityRows(alignmentValues);
  const visibleValueVelocityRows = domainFilter.length
    ? valueVelocityRows.filter(v => domainFilter.includes(v.primaryDomain))
    : valueVelocityRows;
  const velocityWindow = (valueVelocity && valueVelocity.window) || '7d';
  const totalEvents = (valueVelocity && valueVelocity.total_events) || 0;
  const maxCount = Math.max(...liveVelocity.map(v => v.count || 0), 1);
  const maxTodayCount = Math.max(...liveVelocity.map(v => v.today_count || 0), 1);
  const maxBothCount = Math.max(maxCount, maxTodayCount);
  const legendSource = (alignmentDomains.length ? alignmentDomains : Object.entries(TASK_DOMAINS).map(([id, meta]) => ({ id, label: meta.label, color_hex: meta.color })));
  const legendDomains = DASH2_DOMAIN_ORDER.map(id => legendSource.find(d => d.id === id)).filter(Boolean);

  // Drag handlers
  const handleDragStart = (e, task) => {
    setDraggingId(task.id);
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', task.id);
  };
  const handleDragEnd   = () => { setDraggingId(null); setDragOverCol(null); setDragOverSlot(null); };
  const handleSlotChange = col => idx => setDragOverSlot(p => (p?.col === col && p?.idx === idx) ? p : {col, idx});
  const handleDragOver   = col => e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (dragOverCol !== col) setDragOverCol(col); };
  const handleDragLeave  = col => e => { if (e.currentTarget.contains(e.relatedTarget)) return; if (dragOverCol === col) setDragOverCol(null); };

  // Keyboard alternative to drag: persist a horizon move without a drop event.
  const applyMove = (id, targetCol) => {
    setTasks(arr => {
      const dragged = arr.find(t => t.id === id);
      if (!dragged) return arr;
      const rest = arr.filter(t => t.id !== id);
      return [...rest, {...dragged, column: targetCol, due: targetCol !== 'deadline_bound' ? null : dragged.due}];
    });
    const horizon = COL_TO_HORIZON[targetCol];
    if (!horizon) return;
    if (targetCol === 'deadline_bound') {
      setPendingDate({ id });
    } else {
      fetch(`/api/actions/${id}`, {
        method: 'PATCH',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({horizon_state: horizon, due_date: null}),
      }).catch(() => {});
    }
  };

  const handleKeyMove = (id, dir) => {
    const t = tasks.find(x => x.id === id);
    if (!t) return;
    const i = ACTION_COLS.indexOf(t.column);
    if (i < 0) return; // momentum/practice cards are not horizon-movable
    const j = Math.max(0, Math.min(ACTION_COLS.length - 1, i + dir));
    if (j === i) return;
    const target = ACTION_COLS[j];
    applyMove(id, target);
    const targetTitle = (COLUMN_DEFS.find(c => c.id === target) || {}).title || target;
    setLiveMsg(`Moved "${t.title}" to ${targetTitle}`);
  };

  // Shared Acceptance Buffer handlers (Hub inline + TriageDrawer)
  const claimTriageItem = (id, horizon) => {
    const item = triageItems.find(i => i.action_id === id);
    if (!item) return;
    fetch(`/api/actions/${id}/claim`, {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({
        horizon,
        due_date: null,
        ai_refined_title: item.ai_refined_title,
        ai_refined_description: item.ai_refined_description,
        domain: item.domain,
      }),
    })
      .then(r => r.ok ? r.json() : null)
      .then(() => {
        setTriageItems(prev => prev.filter(i => i.action_id !== id));
        const col = HORIZON_TO_COL[horizon] || 'value_aligned';
        setTasks(prev => [...prev, {
          id: id, title: item.ai_refined_title,
          description: item.ai_refined_description || '',
          column: col, score: item.score || 0.5,
          domain: item.domain || 'admin', completed: false,
          due: null, _createdAt: item.created_at || '',
        }]);
        const colTitle = (COLUMN_DEFS.find(c => c.id === col) || {}).title || col;
        setLiveMsg(`Accepted "${item.ai_refined_title}" to ${colTitle}`);
      })
      .catch(() => {});
  };

  const deleteTriageItem = (id) => {
    fetch(`/api/actions/${id}`, {method: 'DELETE'})
      .then(() => setTriageItems(prev => prev.filter(i => i.action_id !== id)))
      .catch(() => {});
  };

  // Rich claim (carries the card's edited title/desc/domain + chosen due date).
  const handleTriageClaim = (id, horizon, title, dueDate, desc, claimedDomain) => {
    fetch(`/api/actions/${id}/claim`, {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({horizon, due_date: dueDate, ai_refined_title: title, ai_refined_description: desc, domain: claimedDomain}),
    })
      .then(r => r.ok ? r.json() : null)
      .then(() => {
        const item = triageItems.find(i => i.action_id === id);
        setTriageItems(prev => prev.filter(i => i.action_id !== id));
        const col = HORIZON_TO_COL[horizon] || 'value_aligned';
        setTasks(prev => [...prev, {
          id: id, title: title || (item && item.ai_refined_title) || '',
          description: desc || (item && item.ai_refined_description) || '',
          column: col, score: (item && item.score) || 0.5,
          domain: claimedDomain || (item && item.domain) || 'admin', completed: false,
          due: dueDate || null, _createdAt: (item && item.created_at) || '',
        }]);
        const colTitle = (COLUMN_DEFS.find(c => c.id === col) || {}).title || col;
        setLiveMsg(`Accepted "${title}" to ${colTitle}`);
      })
      .catch(() => {});
  };

  const handleDrop = (targetCol) => (e) => {
    e.preventDefault();
    const raw = e.dataTransfer.getData('text/plain');
    setDragOverCol(null);
    setDragOverSlot(null);

    // Acceptance card dropped onto a horizon lane → claim it there.
    if (raw && raw.startsWith('triage:')) {
      const horizon = COL_TO_HORIZON[targetCol];
      if (horizon) claimTriageItem(raw.slice(7), horizon);
      return;
    }

    const id = raw;
    const slotIdx = dragOverSlot?.col === targetCol ? dragOverSlot.idx : null;

    // Optimistic move
    setTasks(arr => {
      const dragged = arr.find(t => t.id === id);
      if (!dragged) return arr;
      const rest    = arr.filter(t => t.id !== id);
      const updated = {...dragged, column: targetCol, due: targetCol !== 'deadline_bound' ? null : dragged.due};
      if (slotIdx !== null) {
        const colTasks = rest.filter(t => t.column === targetCol);
        const others   = rest.filter(t => t.column !== targetCol);
        const at = Math.min(slotIdx, colTasks.length);
        return [...others, ...colTasks.slice(0, at), updated, ...colTasks.slice(at)];
      }
      return [...rest, updated];
    });

    const horizon = COL_TO_HORIZON[targetCol];
    if (!horizon) return;

    if (targetCol === 'deadline_bound') {
      // Defer PATCH until user picks a date
      setPendingDate({ id });
    } else {
      fetch(`/api/actions/${id}`, {
        method: 'PATCH',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({horizon_state: horizon, due_date: null}),
      }).catch(() => {});
    }
  };

  const handleDateResolved = (id, date) => {
    setPendingDate(null);
    // Update local state with chosen date
    if (date) setTasks(arr => arr.map(t => t.id === id ? {...t, due: date} : t));
    fetch(`/api/actions/${id}`, {
      method: 'PATCH',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({horizon_state: 'deadline_bound', due_date: date || null}),
    }).catch(() => {});
  };

  // Realize: toggle lifecycle_status
  const handleRealize = (id) => {
    setTasks(arr => arr.map(t => {
      if (t.id !== id) return t;
      const newCompleted = !t.completed;
      fetch(`/api/actions/${id}`, {
        method: 'PATCH',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({lifecycle_status: newCompleted ? 'done' : 'active'}),
      }).catch(() => {});
      return {...t, completed: newCompleted};
    }));
  };

  // Archive (dismiss) — optimistic removal
  const handleDismiss = (id) => {
    if (dismissingId) return;
    const sourceEl = document.querySelector(`[data-task-id="${id}"]`);
    const destEl   = document.querySelector('.archive-col');
    if (sourceEl && destEl) {
      const s = sourceEl.getBoundingClientRect();
      const d = destEl.getBoundingClientRect();
      const tx = (d.left + d.width/2)  - (s.left + s.width/2);
      const ty = (d.top  + d.height/2) - (s.top  + s.height/2);
      sourceEl.style.setProperty('--fly-x', `${tx}px`);
      sourceEl.style.setProperty('--fly-y', `${ty}px`);
      sourceEl.style.cssText += `;position:fixed;left:${s.left}px;top:${s.top}px;width:${s.width}px;margin:0;z-index:9999`;
    }
    setDismissingId(id);
    setTimeout(() => {
      setTasks(arr => arr.filter(t => t.id !== id));
      setDismissingId(null);
    }, 620);
    fetch(`/api/actions/${id}/archive`, {method: 'POST'}).catch(() => {});
  };

  // Suggestion card handlers
  const handleSuggestionAdd = (suggestionId) => {
    fetch(`/api/action-suggestions/${suggestionId}/add`, {method: 'POST'})
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (d && d.action_id) {
          // Optimistically add the new action to Intentions
          fetch('/api/actions/dashboard')
            .then(r => r.ok ? r.json() : null)
            .then(dash => {
              if (!dash) return;
              const newActions = [];
              Object.entries(dash).forEach(([horizon, actions]) => {
                const col = HORIZON_TO_COL[horizon] || 'value_aligned';
                (actions || []).forEach(a => {
                  newActions.push({
                    id: a.action_id, title: a.ai_refined_title, body: a.ai_refined_description || '',
                    segment: a.origin_snippet || '', domain: a.domain || 'admin',
                    score: a.score || 0.5, completed: a.lifecycle_status === 'done',
                    column: col, staleDays: 0,
                    due: a.due_date || undefined,
                    _createdAt: a.created_at,
                  });
                });
              });
              setTasks(newActions);
            }).catch(() => {});
        }
        fetchSuggestion();
      })
      .catch(() => {});
  };

  const handleSuggestionDismiss = (suggestionId, optimistic) => {
    if (!optimistic) {
      // Undo — restore
      fetch(`/api/action-suggestions/${suggestionId}/restore`, {method: 'POST'})
        .then(() => fetchSuggestion())
        .catch(() => {});
    } else {
      // Optimistic dismiss — fetch next suggestion immediately
      fetch(`/api/action-suggestions/${suggestionId}/dismiss`, {method: 'POST'})
        .catch(() => {});
      fetchSuggestion();
    }
  };

  const handleSuggestionSnooze = (suggestionId) => {
    fetch(`/api/action-suggestions/${suggestionId}/snooze`, {method: 'POST'})
      .then(() => fetchSuggestion())
      .catch(() => {});
  };

  const handleSuggestionClearAll = () => {
    fetch('/api/action-suggestions/clear-all', {method: 'POST'})
      .then(() => { setSuggestion(null); setTotalPending(0); })
      .catch(() => {});
  };

  // Archive Done batch
  const handleArchiveDone = () => {
    fetch('/api/actions/archive-done', {method: 'POST'})
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d || d.count === 0) return;
        setTasks(arr => arr.filter(t => !d.action_ids.includes(t.id)));
        clearTimeout(toastTimerRef.current);
        setToast({message: `${d.count} completed action${d.count > 1 ? 's' : ''} archived`, action_ids: d.action_ids});
        toastTimerRef.current = setTimeout(() => setToast(null), 4000);
      })
      .catch(() => {});
  };

  const handleUndoArchiveDone = () => {
    if (!toast) return;
    fetch('/api/actions/undo-archive-done', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({action_ids: toast.action_ids}),
    }).catch(() => {});
    setToast(null);
    // Reload board to restore tasks
    fetch('/api/actions/dashboard')
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        const loaded = [];
        Object.entries(d).forEach(([horizon, actions]) => {
          const col = HORIZON_TO_COL[horizon] || 'focus_zone';
          (actions || []).forEach(a => loaded.push({
            id: a.action_id, column: col, domain: a.domain || 'admin',
            title: a.ai_refined_title, body: a.ai_refined_description || '',
            segment: a.origin_snippet || '', score: a.score || 0.5,
            completed: a.lifecycle_status === 'done', due: a.due_date || null,
            _createdAt: a.created_at || '', staleDays: 0,
          }));
        });
        setTasks(loaded);
      }).catch(() => {});
  };

  // DetailDrawer callbacks
  const handleTaskUpdate = (id, changes) => {
    setTasks(arr => arr.map(t => t.id === id ? {...t, ...changes} : t));
  };
  const handleDrawerDone = (id) => {
    handleRealize(id);
  };
  const handleDrawerArchive = (id) => {
    handleDismiss(id);
  };
  const handleDrawerDelete = (id) => {
    fetch(`/api/actions/${id}`, {method: 'DELETE'}).catch(() => {});
    setTasks(arr => arr.filter(t => t.id !== id));
  };

  // Capture commit
  const handleCaptureCommit = (task) => {
    setTasks(arr => [...arr, task]);
  };

  // Restore from archive drawer
  const handleRestore = (id) => {
    // Reload board since we don't know the horizon_state easily
    fetch('/api/actions/dashboard')
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        const loaded = [];
        Object.entries(d).forEach(([horizon, actions]) => {
          const col = HORIZON_TO_COL[horizon] || 'focus_zone';
          (actions || []).forEach(a => loaded.push({
            id: a.action_id, column: col, domain: a.domain || 'admin',
            title: a.ai_refined_title, body: a.ai_refined_description || '',
            segment: a.origin_snippet || '', score: a.score || 0.5,
            completed: a.lifecycle_status === 'done', due: a.due_date || null,
            _createdAt: a.created_at || '', staleDays: 0,
          }));
        });
        setTasks(loaded);
      }).catch(() => {});
  };

  return (
    <section className="actions-dash actions-dash--dash2">
      <div className="sr-only" role="status" aria-live="polite">{liveMsg}</div>

      <div className="dash2-metrics-grid">
        {ledger ? (
          <ValueLedgerPanel ledger={ledger} expanded={expandedValues}
            onToggle={toggleValueExpand} onDetail={setValueDetail}
            velocityWindow={velocityWindow} totalEvents={totalEvents}
            domainFilter={domainFilter} onLogDay={logPracticeDay} />
        ) : (
        <section className="dash2-capstrip__panel dash2-meter-panel">
          <div className="dash2-capstrip__head">
            <span className="dash2-capstrip__title">Value Velocity ({velocityWindow} Rolling)</span>
            <span className="dash2-capstrip__meta">- {totalEvents} events · {visibleValueVelocityRows.length} values</span>
          </div>
          <div className="dash2-splitaxis-header" aria-hidden="true">
            <span className="dash2-splitaxis-header__past"><span aria-hidden="true">←</span> {velocityWindow} past</span>
            <span className="dash2-splitaxis-header__today">today <span aria-hidden="true">→</span></span>
          </div>
          <div className="dash2-meter-list">
            {visibleValueVelocityRows.map((item, i) => {
              const meta = TASK_DOMAINS[item.domain] || {};
              const color = meta.color || 'var(--gold-1)';
              const domainLabel = meta.label || item.domain;
              const clickable = !!item.valueGoalId;
              return (
                <div key={`${item.label}-${i}`}
                  className={"dash2-meter-row" + (clickable ? " is-clickable" : "")}
                  onClick={clickable ? () => setValueDetail(item) : undefined}
                  onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setValueDetail(item); } } : undefined}
                  tabIndex={clickable ? 0 : undefined}
                  role={clickable ? 'button' : undefined}
                  aria-label={clickable ? `${item.label} — view evidence` : undefined}>
                  {meta.ico && (
                    <span className="dash2-meter-row__glyph"
                      title={domainLabel} aria-hidden="true"
                      style={{"--ico": `url(/dashboard-ui/assets/icons/${meta.ico}.svg)`, background: color}} />
                  )}
                  <span className="dash2-meter-row__label">{item.label}</span>
                  {item.count > 0 && (
                    <span className={`dash2-meter-row__trend is-${item.trend}`} aria-hidden="true">
                      {(item.trend === "up_strong" || item.trend === "up") ? "▲" : item.trend === "down" ? "▼" : ""}
                    </span>
                  )}
                  {item.count === 0 && <span className="dash2-meter-row__trend" />}
                  <span className="dash2-splitaxis"
                    role="meter"
                    aria-valuemin={0}
                    aria-valuenow={item.count}
                    aria-valuemax={maxBothCount}
                    aria-valuetext={`${item.count} events this ${velocityWindow}, ${item.todayCount} today, ${item.trend === 'up_strong' ? 'strong uptrend' : item.trend === 'up' ? 'trending up' : item.trend === 'down' ? 'trending down' : 'flat'}`}>
                    <span className="dash2-splitaxis__bars" aria-hidden="true">
                      <span className="dash2-splitaxis__past"
                        style={{width: `${Math.round(item.count / maxBothCount * 50)}%`, background: color}} />
                      <span className="dash2-splitaxis__center" />
                      <span className="dash2-splitaxis__today"
                        style={{width: `${Math.round(item.todayCount / maxBothCount * 50)}%`, background: color,
                          boxShadow: item.todayCount > 0 ? `0 0 8px ${color}88` : 'none'}} />
                      <span className="dash2-splitaxis__count-past">{item.count}</span>
                      {item.todayCount > 0 && (
                        <span className="dash2-splitaxis__count-today">+{item.todayCount}</span>
                      )}
                    </span>
                  </span>
                </div>
              );
            })}
          </div>
        </section>
        )}

        <section className="dash2-capstrip__panel dash2-mirrors">
          <div className="dash2-capstrip__head">
            <span className="dash2-capstrip__title">Waypoint</span>
            <span className="dash2-capstrip__meta">{waypointDisplayCount ? '1 featured' : '0 plotted'}</span>
          </div>
          {currentWaypoint ? (
            <div className="dash2-mirror-stack">
              <article
                className="dash2-mirror-card is-clickable"
                onClick={() => setWaypointDetail(currentWaypoint)}
                onKeyDown={(e) => {
                  if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault();
                    setWaypointDetail(currentWaypoint);
                  }
                }}
                tabIndex={0}
                role="button"
                aria-label={currentWaypoint.domain + ' waypoint evidence'}
              >
                <span className="dash2-mirror-card__eyebrow">
                  {currentWaypoint.domain || currentWaypoint.valueGoal || 'Waypoint'} - {currentWaypoint.evidenceSummary?.segment_count || 0} moments - {fmtAge(currentWaypoint.staleHours)}
                </span>
                <blockquote className="dash2-mirror-card__quote">{currentWaypoint.headline || currentWaypoint.description || ''}</blockquote>
                <p className="dash2-mirror-card__desc">{currentWaypoint.description || ''}</p>
                <div className="dash2-mirror-card__evidence">
                  <span>Evidence</span>
                  <span>{currentWaypoint.evidenceSummary?.segment_count || 0} segments</span>
                  <span>{currentWaypoint.evidenceSummary?.repeated_behavior_count || 0} repeated behavior</span>
                  <span>{currentWaypoint.evidenceSummary?.action_count || 0} actions</span>
                </div>
                <span className="dash2-mirror-card__link">View evidence</span>
              </article>
            </div>
          ) : <p className="dash2-empty">No coordinates plotted yet.</p>}
        </section>
      </div>

      {!ledger && (
      <MomentumLog
        practices={domainFilter.length ? momentumRegistry.filter(p => domainFilter.includes(p.domain)) : momentumRegistry}
        rhythm={momentumRhythm}
        days={momentumDays}
        expanded={momentumExpanded}
        onToggleExpand={() => setMomentumExpanded(v => !v)}
        onReorder={handleMomentumReorder}
        onCellClick={handleCellClick}
        onAddPractice={handleAddPractice}
        busy={momentumBusy}
      />
      )}

      <div className="actions-dash__board actions-dash__board--dash2">
        <div className="cols cols--dash2" role="list">
          {COLUMN_DEFS.map(def => (
            def.id === 'acceptance' ? (
              (domainFilter.length ? triageItems.filter(t => domainFilter.includes(t.domain||'admin')) : triageItems).length === 0 ? null : (
              <AcceptanceColumn
                key={def.id}
                def={def}
                items={domainFilter.length ? triageItems.filter(t => domainFilter.includes(t.domain||'admin')) : triageItems}
                onClaim={claimTriageItem}
                onDelete={deleteTriageItem}
                onOpen={(i) => setTriageStep({ index: i })}
                isExpanded={expandedLane === def.id}
                isCollapsed={expandedLane !== def.id}
                onToggleExpand={handleToggleLane}
              />
            )) : (
            <Column
              key={def.id}
              def={def}
              tasks={tasksByCol(def.id)}
              dragOver={dragOverCol === def.id}
              draggingId={draggingId}
              isDragging={!!draggingId}
              activeSlot={dragOverSlot?.col === def.id ? dragOverSlot.idx : null}
              onSlotChange={handleSlotChange(def.id)}
              realizingId={realizingId}
              dismissingId={dismissingId}
              onDragOver={handleDragOver(def.id)}
              onDragLeave={handleDragLeave(def.id)}
              onDrop={handleDrop(def.id)}
              onDragStart={handleDragStart}
              onDragEnd={handleDragEnd}
              onTitleClick={setDetailTask}
              onRealize={handleRealize}
              onDismiss={handleDismiss}
              sortKey={colSorts[def.id] || 'manual'}
              onSortChange={key => setSortFor(def.id, key)}
              pendingDateId={pendingDate?.id}
              onDateResolved={handleDateResolved}
              isExpanded={expandedLane === def.id}
              isCollapsed={expandedLane !== def.id}
              onToggleExpand={handleToggleLane}
              onMove={handleKeyMove}
            />
            )
          ))}
        </div>

        <div className="bucket-tray">
          <ArchiveDrawer onRestore={handleRestore} onDismiss={handleDismiss} />
        </div>
      </div>

      {showTriage && triageItems.length > 0 && (
        <TriageDrawer
          items={triageItems}
          onClose={() => setShowTriage(false)}
          onClaim={(id, horizon, title, dueDate, desc, claimedDomain) => {
            fetch(`/api/actions/${id}/claim`, {
              method: 'POST',
              headers: {'Content-Type':'application/json'},
              body: JSON.stringify({horizon, due_date: dueDate, ai_refined_title: title, ai_refined_description: desc, domain: claimedDomain}),
            })
              .then(r => r.ok ? r.json() : null)
              .then(() => {
                setTriageItems(prev => prev.filter(i => i.action_id !== id));
                const col = HORIZON_TO_COL[horizon] || 'value_aligned';
                const item = triageItems.find(i => i.action_id === id);
                if (item) {
                  setTasks(prev => [...prev, {
                    id: id, title: title || item.ai_refined_title,
                    description: item.ai_refined_description || '',
                    column: col, score: item.score || 0.5,
                    domain: claimedDomain || item.domain || 'admin', completed: false,
                    due: dueDate || null, _createdAt: item.created_at || '',
                  }]);
                }
              })
              .catch(() => {});
          }}
          onDelete={(id) => deleteTriageItem(id)}
          onDeleteAll={() => {
            const saved = [...triageItems];
            fetch('/api/actions/pending-review/delete-all', {method: 'POST'})
              .then(() => {
                setTriageItems([]);
                setShowTriage(false);
                clearTimeout(toastTimerRef.current);
                setToast({
                  message: `${saved.length} queued item${saved.length !== 1 ? 's' : ''} deleted`,
                  onUndo: () => {
                    Promise.all(saved.map(item =>
                      fetch('/api/actions', {
                        method: 'POST',
                        headers: {'Content-Type':'application/json'},
                        body: JSON.stringify({
                          ai_refined_title: item.ai_refined_title,
                          ai_refined_description: item.ai_refined_description,
                          domain: item.domain,
                          score: item.score,
                          horizon_state: item.horizon_state || 'value_aligned',
                          lifecycle_status: 'pending_review',
                        }),
                      })
                    )).then(() => fetchTriageItems()).catch(() => {});
                    setToast(null);
                  },
                });
                toastTimerRef.current = setTimeout(() => setToast(null), 6000);
              })
              .catch(() => {});
          }}
        />
      )}

      {valueDetail && (
        <ValueDrawer item={valueDetail} onClose={() => setValueDetail(null)} />
      )}

      {waypointDetail && (
        <WaypointDrawer waypoint={waypointDetail} onClose={() => setWaypointDetail(null)} />
      )}

      {triageStep && triageItems.length > 0 && (
        <TriageStepper
          items={triageItems}
          startIndex={triageStep.index}
          onClaim={handleTriageClaim}
          onDelete={deleteTriageItem}
          onClose={() => setTriageStep(null)}
        />
      )}

      {dayDetail && window.DailyDetailModal && React.createElement(window.DailyDetailModal, {
        key: dayDetail.practiceId + dayDetail.entryDate,
        practiceId: dayDetail.practiceId,
        entryDate: dayDetail.entryDate,
        registry: momentumRegistry,
        onClose: () => { setDayDetail(null); refetchMomentumLog(); },
        onMoved: refetchMomentumLog,
      })}

      {captureFor && (
        <CaptureModal
          initialColumn={captureFor === 'any' ? null : captureFor}
          initialType={captureType}
          onCommit={handleCaptureCommit}
          onPracticeCreated={() => {
            fetch('/api/momentum/registry')
              .then(r => r.ok ? r.json() : [])
              .then(setMomentumRegistry)
              .catch(() => {});
          }}
          onClose={() => setCaptureFor(null)}
        />
      )}

      {detailTask && (
        <DetailDrawer
          task={detailTask}
          onClose={() => setDetailTask(null)}
          onTaskUpdate={handleTaskUpdate}
          onDone={handleDrawerDone}
          onArchive={handleDrawerArchive}
          onDelete={handleDrawerDelete}
        />
      )}

      {toast && (
        <UndoToast
          message={toast.message}
          onUndo={toast.onUndo || handleUndoArchiveDone}
          onDismiss={() => setToast(null)}
        />
      )}
    </section>
  );
};

window.ActionStream = ActionStream;
