          {agents.map((agent) => {
            const agentStatus = finalStatuses[agent.id] || { status: 'offline' };
            return (
              <AgentCard
                key={agent.id}
                agent={agent}
                status={agentStatus.status}
                task={agentStatus.task}
                uptime={agentStatus.uptime}
                onClick={() => navigate(`/fleet/${agent.id}`)}
              />
            );
          })}

/**
 * Command Centre — Home Page
 * ==========================
 * Everything at a glance. The single pane of glass.
 *
 * Sections:
 * 1. Mission Statement — what the fleet is working towards
 * 2. Your Tasks — tasks agents have assigned to Michael (bidirectional)
 * 3. Quick Stats — agents online, tasks, messages, cost
 * 4. Agent Grid — 8 agent cards with live status
 * 5. Pending Approvals + Activity Feed side by side
 */
import { useNavigate } from 'react-router-dom';
import { getAgents, getAgent, getBranding, getConfig } from '../lib/fleet';
import AgentCard from '../components/fleet/AgentCard';
import QuickStats from '../components/dashboard/QuickStats';
import PendingApprovals from '../components/dashboard/PendingApprovals';
import ActivityFeed from '../components/dashboard/ActivityFeed';
import { Target, CheckCircle2, Clock, ArrowRight } from 'lucide-react';
import { useAgentSnapshots, useTasks, useApproveAction, useRejectAction } from '../hooks/useSupabase';

// Fallback demo statuses (used if Supabase fails)
const DEMO_STATUSES = {
  rivet: { status: 'healthy', task: 'Monitoring fleet health, processing heartbeats', uptime: '14d 3h' },
  builder: { status: 'busy', task: 'Deploying Control Centre v0.1', uptime: '14d 3h' },
  susan: { status: 'healthy', task: 'Running SMS sequence batch #47', uptime: '12d 8h' },
  harper: { status: 'healthy', task: 'Idle — awaiting content task', uptime: '10d 1h' },
  sentinel: { status: 'warning', task: 'RAM at 78% — monitoring', uptime: '14d 3h' },
  radar: { status: 'healthy', task: 'Competitor scan: Hipages pricing', uptime: '8d 14h' },
  herald: { status: 'healthy', task: 'Idle — last alert sent 2h ago', uptime: '14d 3h' },
  cog: { status: 'offline', task: null, uptime: null },
};

// Fallback: Tasks assigned TO Michael by agents
const MY_TASKS_FALLBACK = [
  { id: 1, title: 'Review Hipages pricing analysis', from: 'radar', priority: 'high', description: 'Hipages raised Gold tier 15%. Opportunity to position as value alternative.' },
  { id: 2, title: 'Approve SMS re-engagement campaign', from: 'susan', priority: 'urgent', description: '45 cold leads dormant >30 days. Susan has the sequence ready.' },
  { id: 3, title: 'Call lead #4821 — they responded', from: 'susan', priority: 'high', description: 'Hot lead, responded to SMS. Personal call recommended.' },
  { id: 4, title: 'Review VPS security audit', from: 'sentinel', priority: 'medium', description: '3 unnecessary ports open. Sentinel recommends closing them.' },
];

const priorityStyles = {
  urgent: 'text-red-400 bg-red-500/10 border-red-500/20',
  high: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
  medium: 'text-blue-400 bg-blue-500/10 border-blue-500/20',
  low: 'text-[var(--cc-text-muted)] bg-[var(--cc-surface)] border-[var(--cc-border)]',
};

export default function CommandCentre() {
  const navigate = useNavigate();
  const agents = getAgents();
  const config = getConfig();

  // Fetch real data from Supabase
  const { data: snapshots } = useAgentSnapshots();
  const { data: allTasks } = useTasks();
  const approveAction = useApproveAction();
  const rejectAction = useRejectAction();

  // Build agent statuses from snapshots (fallback to demo if empty)
  const agentStatuses = {};
  if (snapshots && snapshots.length > 0) {
    snapshots.forEach(snap => {
      agentStatuses[snap.agent_name] = {
        status: snap.status || 'offline',
        task: snap.current_task || null,
        uptime: snap.uptime_hours ? `${Math.floor(snap.uptime_hours / 24)}d ${Math.floor(snap.uptime_hours % 24)}h` : null,
      };
    });
  }
  const finalStatuses = Object.keys(agentStatuses).length > 0 ? agentStatuses : DEMO_STATUSES;

  // Filter tasks assigned to 'michael' (my tasks)
  const MY_TASKS = allTasks && allTasks.length > 0
    ? allTasks.filter(t => t.assigned_to === 'michael').slice(0, 4).map(t => ({
        id: t.id,
        title: t.title,
        from: t.agent_id,
        priority: t.priority || 'medium',
        description: t.description || '',
      }))
    : MY_TASKS_FALLBACK;

  // Count agents online
  const agentsOnline = Object.values(finalStatuses).filter(s => s.status !== 'offline').length;

  return (

      {/* ── Mission Statement ────────────────────────────── */}
      {config.mission && (

          <Target className="w-5 h-5 text-[var(--cc-accent)] flex-shrink-0" />

            Mission:{' '}
            {config.mission}

      )}

      {/* ── Your Tasks (from agents) ─────────────────────── */}
      {MY_TASKS.length > 0 && (

              <Clock className="w-4 h-4" />
              Your Tasks

                {MY_TASKS.length}

            <button
              onClick={() => navigate('/tasks')}
              className="text-[11px] text-[var(--cc-accent)] hover:underline flex items-center gap-1"
            >
              View all <ArrowRight className="w-3 h-3" />
            </button>

            {MY_TASKS.map((task) => {
              const fromAgent = getAgent(task.from);
              return (

                    <button className="w-5 h-5 rounded-full border-2 border-[var(--cc-border)] hover:border-emerald-400 hover:bg-emerald-400/10 flex-shrink-0 mt-0.5 transition-colors flex items-center justify-center group">
                      <CheckCircle2 className="w-3 h-3 text-emerald-400 opacity-0 group-hover:opacity-100 transition-opacity" />
                    </button>

{task.title}

{task.description}

                        {fromAgent?.avatar}
                        from {fromAgent?.name}
                        <span className={`text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded-full ${priorityStyles[task.priority]}`}>
                          {task.priority}

              );
            })}

      )}

      {/* ── Quick Stats ────────────────────────────────── */}
      <QuickStats
        stats={{
          agentsOnline,
          tasksCompleted: allTasks ? allTasks.filter(t => t.status === 'done').length : 23,
          messagesTotal: 147, // TODO: wire to cc_council_messages count
          apiCostToday: 4.82, // TODO: wire to cc_api_costs today sum
        }}
      />

      {/* ── Agent Grid ─────────────────────────────────── */}

          Fleet Status

          {agents.map((agent) => {
            const demo = DEMO_STATUSES[agent.id] || { status: 'offline' };
            return (
              <AgentCard
                key={agent.id}
                agent={agent}
                status={demo.status}
                task={demo.task}
                uptime={demo.uptime}
                onClick={() => navigate(`/fleet/${agent.id}`)}
              />
            );
          })}

      {/* ── Two Column: Approvals + Activity ───────────── */}

        <PendingApprovals />
        <ActivityFeed />

  );
}

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">

-            const demo = DEMO_STATUSES[agent.id] || { status: 'offline' };

+            const agentStatus = finalStatuses[agent.id] || { status: 'offline' };

+                status={agentStatus.status}

+                task={agentStatus.task}

+                uptime={agentStatus.uptime}


Edit

      {/* ── Two Column: Approvals + Activity ───────────── */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <PendingApprovals />
        <ActivityFeed />
      </div>

      {/* ── Two Column: Approvals + Activity ───────────── */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <PendingApprovals
          onApprove={(id) => approveAction.mutate({ id })}
          onReject={(id) => rejectAction.mutate({ id })}
        />
        <ActivityFeed />
      </div>


The file /home/ccuser/opsman-control-centre/src/pages/CommandCentre.jsx has been updated successfully.

      {/* ── Two Column: Approvals + Activity ───────────── */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <PendingApprovals />
        <ActivityFeed />
      </div>

      {/* ── Two Column: Approvals + Activity ───────────── */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <PendingApprovals
          onApprove={(id) => approveAction.mutate({ id })}
          onReject={(id) => rejectAction.mutate({ id })}
        />
        <ActivityFeed />
      </div>

/**
 * Command Centre — Home Page
 * ==========================
 * Everything at a glance. The single pane of glass.
 *
 * Sections:
 * 1. Mission Statement — what the fleet is working towards
 * 2. Your Tasks — tasks agents have assigned to Michael (bidirectional)
 * 3. Quick Stats — agents online, tasks, messages, cost
 * 4. Agent Grid — 8 agent cards with live status
 * 5. Pending Approvals + Activity Feed side by side
 */
import { useNavigate } from 'react-router-dom';
import { getAgents, getAgent, getBranding, getConfig } from '../lib/fleet';
import AgentCard from '../components/fleet/AgentCard';
import QuickStats from '../components/dashboard/QuickStats';
import PendingApprovals from '../components/dashboard/PendingApprovals';
import ActivityFeed from '../components/dashboard/ActivityFeed';
import { Target, CheckCircle2, Clock, ArrowRight } from 'lucide-react';
import { useAgentSnapshots, useTasks, useApproveAction, useRejectAction } from '../hooks/useSupabase';

// Fallback demo statuses (used if Supabase fails)
const DEMO_STATUSES = {
  rivet: { status: 'healthy', task: 'Monitoring fleet health, processing heartbeats', uptime: '14d 3h' },
  builder: { status: 'busy', task: 'Deploying Control Centre v0.1', uptime: '14d 3h' },
  susan: { status: 'healthy', task: 'Running SMS sequence batch #47', uptime: '12d 8h' },
  harper: { status: 'healthy', task: 'Idle — awaiting content task', uptime: '10d 1h' },
  sentinel: { status: 'warning', task: 'RAM at 78% — monitoring', uptime: '14d 3h' },
  radar: { status: 'healthy', task: 'Competitor scan: Hipages pricing', uptime: '8d 14h' },
  herald: { status: 'healthy', task: 'Idle — last alert sent 2h ago', uptime: '14d 3h' },
  cog: { status: 'offline', task: null, uptime: null },
};

// Fallback: Tasks assigned TO Michael by agents
const MY_TASKS_FALLBACK = [
  { id: 1, title: 'Review Hipages pricing analysis', from: 'radar', priority: 'high', description: 'Hipages raised Gold tier 15%. Opportunity to position as value alternative.' },
  { id: 2, title: 'Approve SMS re-engagement campaign', from: 'susan', priority: 'urgent', description: '45 cold leads dormant >30 days. Susan has the sequence ready.' },
  { id: 3, title: 'Call lead #4821 — they responded', from: 'susan', priority: 'high', description: 'Hot lead, responded to SMS. Personal call recommended.' },
  { id: 4, title: 'Review VPS security audit', from: 'sentinel', priority: 'medium', description: '3 unnecessary ports open. Sentinel recommends closing them.' },
];

const priorityStyles = {
  urgent: 'text-red-400 bg-red-500/10 border-red-500/20',
  high: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
  medium: 'text-blue-400 bg-blue-500/10 border-blue-500/20',
  low: 'text-[var(--cc-text-muted)] bg-[var(--cc-surface)] border-[var(--cc-border)]',
};

export default function CommandCentre() {
  const navigate = useNavigate();
  const agents = getAgents();
  const config = getConfig();

  // Fetch real data from Supabase
  const { data: snapshots } = useAgentSnapshots();
  const { data: allTasks } = useTasks();
  const approveAction = useApproveAction();
  const rejectAction = useRejectAction();

  // Build agent statuses from snapshots (fallback to demo if empty)
  const agentStatuses = {};
  if (snapshots && snapshots.length > 0) {
    snapshots.forEach(snap => {
      agentStatuses[snap.agent_name] = {
        status: snap.status || 'offline',
        task: snap.current_task || null,
        uptime: snap.uptime_hours ? `${Math.floor(snap.uptime_hours / 24)}d ${Math.floor(snap.uptime_hours % 24)}h` : null,
      };
    });
  }
  const finalStatuses = Object.keys(agentStatuses).length > 0 ? agentStatuses : DEMO_STATUSES;

  // Filter tasks assigned to 'michael' (my tasks)
  const MY_TASKS = allTasks && allTasks.length > 0
    ? allTasks.filter(t => t.assigned_to === 'michael').slice(0, 4).map(t => ({
        id: t.id,
        title: t.title,
        from: t.agent_id,
        priority: t.priority || 'medium',
        description: t.description || '',
      }))
    : MY_TASKS_FALLBACK;

  // Count agents online
  const agentsOnline = Object.values(finalStatuses).filter(s => s.status !== 'offline').length;

  return (

      {/* ── Mission Statement ────────────────────────────── */}
      {config.mission && (

          <Target className="w-5 h-5 text-[var(--cc-accent)] flex-shrink-0" />

            Mission:{' '}
            {config.mission}

      )}

      {/* ── Your Tasks (from agents) ─────────────────────── */}
      {MY_TASKS.length > 0 && (

              <Clock className="w-4 h-4" />
              Your Tasks

                {MY_TASKS.length}

            <button
              onClick={() => navigate('/tasks')}
              className="text-[11px] text-[var(--cc-accent)] hover:underline flex items-center gap-1"
            >
              View all <ArrowRight className="w-3 h-3" />
            </button>

            {MY_TASKS.map((task) => {
              const fromAgent = getAgent(task.from);
              return (

                    <button className="w-5 h-5 rounded-full border-2 border-[var(--cc-border)] hover:border-emerald-400 hover:bg-emerald-400/10 flex-shrink-0 mt-0.5 transition-colors flex items-center justify-center group">
                      <CheckCircle2 className="w-3 h-3 text-emerald-400 opacity-0 group-hover:opacity-100 transition-opacity" />
                    </button>

{task.title}

{task.description}

                        {fromAgent?.avatar}
                        from {fromAgent?.name}
                        <span className={`text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded-full ${priorityStyles[task.priority]}`}>
                          {task.priority}

              );
            })}

      )}

      {/* ── Quick Stats ────────────────────────────────── */}
      <QuickStats
        stats={{
          agentsOnline,
          tasksCompleted: allTasks ? allTasks.filter(t => t.status === 'done').length : 23,
          messagesTotal: 147, // TODO: wire to cc_council_messages count
          apiCostToday: 4.82, // TODO: wire to cc_api_costs today sum
        }}
      />

      {/* ── Agent Grid ─────────────────────────────────── */}

          Fleet Status

          {agents.map((agent) => {
            const agentStatus = finalStatuses[agent.id] || { status: 'offline' };
            return (
              <AgentCard
                key={agent.id}
                agent={agent}
                status={agentStatus.status}
                task={agentStatus.task}
                uptime={agentStatus.uptime}
                onClick={() => navigate(`/fleet/${agent.id}`)}
              />
            );
          })}

      {/* ── Two Column: Approvals + Activity ───────────── */}

        <PendingApprovals />
        <ActivityFeed />

  );
}

+          onApprove={(id) => approveAction.mutate({ id })}

+          onReject={(id) => rejectAction.mutate({ id })}


Now let me wire up the ActivityFeed and PendingApprovals components:


import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';
import { useActivityFeed } from '../../hooks/useSupabase';


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


export default function ActivityFeed({ events = [] }) {
  // Demo data if no events provided (remove once API is connected)
  const displayEvents = events.length > 0 ? events : [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

export default function ActivityFeed({ events = [] }) {
  const { data: activityData } = useActivityFeed(20);

  // Fallback demo data
  const FALLBACK_EVENTS = [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

  // Map Supabase data to component format
  const mappedEvents = activityData && activityData.length > 0
    ? activityData.map(a => ({
        id: a.id,
        agent: a.agent_id,
        action: a.action,
        detail: a.details,
        timestamp: new Date(a.created_at),
        type: a.event_type || 'system',
      }))
    : [];

  const displayEvents = events.length > 0 ? events : (mappedEvents.length > 0 ? mappedEvents : FALLBACK_EVENTS);


<tool_use_error>Sibling tool call errored</tool_use_error>


1→/**
     2→ * ActivityFeed
     3→ * ============
     4→ * Real-time scrolling feed of fleet activity.
     5→ * Shows the last N actions from all agents.
     6→ *
     7→ * Each event has: { id, agent, action, detail, timestamp, type }
     8→ *   type: 'task' | 'message' | 'error' | 'deploy' | 'system'
     9→ *
    10→ * EXTENDING: Add new event types to the typeStyles map below.
    11→ */
    12→import Card, { CardHeader } from '../ui/Card';
    13→import { Activity } from 'lucide-react';
    14→import { getAgent } from '../../lib/fleet';
    15→
    16→const typeStyles = {
    17→  task:    'border-l-blue-500',
    18→  message: 'border-l-purple-500',
    19→  error:   'border-l-red-500',
    20→  deploy:  'border-l-emerald-500',
    21→  system:  'border-l-amber-500',
    22→};
    23→
    24→function timeAgo(timestamp) {
    25→  if (!timestamp) return '';
    26→  const diff = Date.now() - new Date(timestamp).getTime();
    27→  const mins = Math.floor(diff / 60000);
    28→  if (mins < 1) return 'just now';
    29→  if (mins < 60) return `${mins}m ago`;
    30→  const hours = Math.floor(mins / 60);
    31→  if (hours < 24) return `${hours}h ago`;
    32→  return `${Math.floor(hours / 24)}d ago`;
    33→}
    34→
    35→export default function ActivityFeed({ events = [] }) {
    36→  // Demo data if no events provided (remove once API is connected)
    37→  const displayEvents = events.length > 0 ? events : [
    38→    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    39→    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    40→    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    41→    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    42→    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    43→    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    44→    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    45→    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
    46→  ];
    47→
    48→  return (
    49→    <Card>
    50→      <CardHeader title="Activity Feed" subtitle="Fleet-wide" icon={Activity} />
    51→

    52→        {displayEvents.map((event) => {
    53→          const agent = getAgent(event.agent);
    54→          return (
    55→

    61→              {/* Agent avatar */}
    62→
    63→                {agent?.avatar || '🤖'}
    64→
    65→
    66→              {/* Content */}
    67→

    68→

    69→
    70→                    {agent?.name || event.agent}
    71→
    72→                  {' '}
    73→                  {event.action}
    74→

    75→                {event.detail && (
    76→

    77→                    {event.detail}
    78→

    79→                )}
    80→

    81→
    82→              {/* Timestamp */}
    83→
    84→                {timeAgo(event.timestamp)}
    85→
    86→

    87→          );
    88→        })}
    89→

    90→    </Card>
    91→  );
    92→}
    93→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

/**
 * ActivityFeed
 * ============
 * Real-time scrolling feed of fleet activity.
 * Shows the last N actions from all agents.
 *
 * Each event has: { id, agent, action, detail, timestamp, type }
 *   type: 'task' | 'message' | 'error' | 'deploy' | 'system'
 *
 * EXTENDING: Add new event types to the typeStyles map below.
 */
import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

const typeStyles = {
  task:    'border-l-blue-500',
  message: 'border-l-purple-500',
  error:   'border-l-red-500',
  deploy:  'border-l-emerald-500',
  system:  'border-l-amber-500',
};

function timeAgo(timestamp) {
  if (!timestamp) return '';
  const diff = Date.now() - new Date(timestamp).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  return `${Math.floor(hours / 24)}d ago`;
}

export default function ActivityFeed({ events = [] }) {
  // Demo data if no events provided (remove once API is connected)
  const displayEvents = events.length > 0 ? events : [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

  return (
    <Card>
      <CardHeader title="Activity Feed" subtitle="Fleet-wide" icon={Activity} />

        {displayEvents.map((event) => {
          const agent = getAgent(event.agent);
          return (

              {/* Agent avatar */}

                {agent?.avatar || '🤖'}

              {/* Content */}

                    {agent?.name || event.agent}

                  {' '}
                  {event.action}

                {event.detail && (

                    {event.detail}

                )}

              {/* Timestamp */}

                {timeAgo(event.timestamp)}

          );
        })}

    </Card>
  );
}


import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';
import { useActivityFeed } from '../../hooks/useSupabase';


The file /home/ccuser/opsman-control-centre/src/components/dashboard/ActivityFeed.jsx has been updated successfully.

import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';
import { useActivityFeed } from '../../hooks/useSupabase';

/**
 * ActivityFeed
 * ============
 * Real-time scrolling feed of fleet activity.
 * Shows the last N actions from all agents.
 *
 * Each event has: { id, agent, action, detail, timestamp, type }
 *   type: 'task' | 'message' | 'error' | 'deploy' | 'system'
 *
 * EXTENDING: Add new event types to the typeStyles map below.
 */
import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

const typeStyles = {
  task:    'border-l-blue-500',
  message: 'border-l-purple-500',
  error:   'border-l-red-500',
  deploy:  'border-l-emerald-500',
  system:  'border-l-amber-500',
};

function timeAgo(timestamp) {
  if (!timestamp) return '';
  const diff = Date.now() - new Date(timestamp).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  return `${Math.floor(hours / 24)}d ago`;
}

export default function ActivityFeed({ events = [] }) {
  // Demo data if no events provided (remove once API is connected)
  const displayEvents = events.length > 0 ? events : [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

  return (
    <Card>
      <CardHeader title="Activity Feed" subtitle="Fleet-wide" icon={Activity} />

        {displayEvents.map((event) => {
          const agent = getAgent(event.agent);
          return (

              {/* Agent avatar */}

                {agent?.avatar || '🤖'}

              {/* Content */}

                    {agent?.name || event.agent}

                  {' '}
                  {event.action}

                {event.detail && (

                    {event.detail}

                )}

              {/* Timestamp */}

                {timeAgo(event.timestamp)}

          );
        })}

    </Card>
  );
}

+import { useActivityFeed } from '../../hooks/useSupabase';


export default function ActivityFeed({ events = [] }) {
  // Demo data if no events provided (remove once API is connected)
  const displayEvents = events.length > 0 ? events : [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

export default function ActivityFeed({ events = [] }) {
  const { data: activityData } = useActivityFeed(20);

  // Fallback demo data
  const FALLBACK_EVENTS = [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

  // Map Supabase data to component format
  const mappedEvents = activityData && activityData.length > 0
    ? activityData.map(a => ({
        id: a.id,
        agent: a.agent_id,
        action: a.action,
        detail: a.details,
        timestamp: new Date(a.created_at),
        type: a.event_type || 'system',
      }))
    : [];

  const displayEvents = events.length > 0 ? events : (mappedEvents.length > 0 ? mappedEvents : FALLBACK_EVENTS);


export default function ActivityFeed({ events = [] }) {
  const { data: activityData } = useActivityFeed(20);

  // Fallback demo data
  const FALLBACK_EVENTS = [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

  // Map Supabase data to component format
  const mappedEvents = activityData && activityData.length > 0
    ? activityData.map(a => ({
        id: a.id,
        agent: a.agent_id,
        action: a.action,
        detail: a.details,
        timestamp: new Date(a.created_at),
        type: a.event_type || 'system',
      }))
    : [];

  const displayEvents = events.length > 0 ? events : (mappedEvents.length > 0 ? mappedEvents : FALLBACK_EVENTS);

/**
 * ActivityFeed
 * ============
 * Real-time scrolling feed of fleet activity.
 * Shows the last N actions from all agents.
 *
 * Each event has: { id, agent, action, detail, timestamp, type }
 *   type: 'task' | 'message' | 'error' | 'deploy' | 'system'
 *
 * EXTENDING: Add new event types to the typeStyles map below.
 */
import Card, { CardHeader } from '../ui/Card';
import { Activity } from 'lucide-react';
import { getAgent } from '../../lib/fleet';
import { useActivityFeed } from '../../hooks/useSupabase';

const typeStyles = {
  task:    'border-l-blue-500',
  message: 'border-l-purple-500',
  error:   'border-l-red-500',
  deploy:  'border-l-emerald-500',
  system:  'border-l-amber-500',
};

function timeAgo(timestamp) {
  if (!timestamp) return '';
  const diff = Date.now() - new Date(timestamp).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  return `${Math.floor(hours / 24)}d ago`;
}

export default function ActivityFeed({ events = [] }) {
  // Demo data if no events provided (remove once API is connected)
  const displayEvents = events.length > 0 ? events : [
    { id: 1, agent: 'rivet', action: 'Spawned sub-agent', detail: 'DeepSeek research task for lead analysis', timestamp: new Date(Date.now() - 120000), type: 'system' },
    { id: 2, agent: 'susan', action: 'SMS sequence sent', detail: 'Batch of 12 follow-up messages', timestamp: new Date(Date.now() - 300000), type: 'message' },
    { id: 3, agent: 'builder', action: 'Deployed update', detail: 'Growth Engine v2.3.1 — fixed SMS retry logic', timestamp: new Date(Date.now() - 600000), type: 'deploy' },
    { id: 4, agent: 'sentinel', action: 'Alert cleared', detail: 'RAM usage back below 80%', timestamp: new Date(Date.now() - 900000), type: 'system' },
    { id: 5, agent: 'harper', action: 'Content published', detail: 'LinkedIn post: "Why RateRight exists"', timestamp: new Date(Date.now() - 1800000), type: 'task' },
    { id: 6, agent: 'radar', action: 'Research complete', detail: 'Competitor analysis: Hipages Q1 strategy', timestamp: new Date(Date.now() - 3600000), type: 'task' },
    { id: 7, agent: 'cog', action: 'Cron job completed', detail: 'Nightly lead scan processed 847 leads', timestamp: new Date(Date.now() - 7200000), type: 'system' },
    { id: 8, agent: 'herald', action: 'Notification sent', detail: 'Telegram alert to Michael: daily summary', timestamp: new Date(Date.now() - 10800000), type: 'message' },
  ];

  return (
    <Card>
      <CardHeader title="Activity Feed" subtitle="Fleet-wide" icon={Activity} />

        {displayEvents.map((event) => {
          const agent = getAgent(event.agent);
          return (

              {/* Agent avatar */}

                {agent?.avatar || '🤖'}

              {/* Content */}

                    {agent?.name || event.agent}

                  {' '}
                  {event.action}

                {event.detail && (

                    {event.detail}

                )}

              {/* Timestamp */}

                {timeAgo(event.timestamp)}

          );
        })}

    </Card>
  );
}

-  // Demo data if no events provided (remove once API is connected)

-  const displayEvents = events.length > 0 ? events : [

+  const { data: activityData } = useActivityFeed(20);

+  // Map Supabase data to component format

+  const mappedEvents = activityData && activityData.length > 0

+        timestamp: new Date(a.created_at),

+        type: a.event_type || 'system',

+  const displayEvents = events.length > 0 ? events : (mappedEvents.length > 0 ? mappedEvents : FALLBACK_EVENTS);


1→/**
     2→ * PendingApprovals
     3→ * ================
     4→ * Shows tasks/actions that need Michael's approval.
     5→ * Agents propose → this shows them → Michael approves/rejects inline.
     6→ *
     7→ * Props:
     8→ *   approvals — array of { id, agent, action, description, createdAt }
     9→ *   onApprove — (id) => void
    10→ *   onReject  — (id) => void
    11→ */
    12→import Card, { CardHeader } from '../ui/Card';
    13→import { ShieldCheck, Check, X } from 'lucide-react';
    14→import { getAgent } from '../../lib/fleet';
    15→
    16→export default function PendingApprovals({ approvals = [], onApprove, onReject }) {
    17→  // Demo data
    18→  const displayApprovals = approvals.length > 0 ? approvals : [
    19→    { id: 1, agent: 'builder', action: 'Deploy Growth Engine v2.4', description: 'New SMS retry logic + webhook hardening', createdAt: new Date(Date.now() - 600000) },
    20→    { id: 2, agent: 'susan', action: 'Send batch SMS to 45 cold leads', description: 'Re-engagement campaign for leads dormant >30 days', createdAt: new Date(Date.now() - 1200000) },
    21→    { id: 3, agent: 'cog', action: 'Upgrade Node.js to v22', description: 'Current v20 hitting memory limits with 8 agents', createdAt: new Date(Date.now() - 3600000) },
    22→  ];
    23→
    24→  if (displayApprovals.length === 0) return null;
    25→
    26→  return (
    27→    <Card>
    28→      <CardHeader
    29→        title="Pending Approvals"
    30→        subtitle={`${displayApprovals.length} awaiting`}
    31→        icon={ShieldCheck}
    32→      />
    33→

    34→        {displayApprovals.map((item) => {
    35→          const agent = getAgent(item.agent);
    36→          return (
    37→

    38→              {agent?.avatar || '🤖'}
    39→

    40→

    41→                  {item.action}
    42→

    43→

    44→                  {agent?.name}
    45→                  {' — '}
    46→                  {item.description}
    47→

    48→

    49→

    50→                <button
    51→                  onClick={() => onApprove?.(item.id)}
    52→                  className="w-7 h-7 rounded-lg bg-emerald-500/15 hover:bg-emerald-500/25 flex items-center justify-center transition-colors"
    53→                  title="Approve"
    54→                >
    55→                  <Check className="w-3.5 h-3.5 text-emerald-400" />
    56→                </button>
    57→                <button
    58→                  onClick={() => onReject?.(item.id)}
    59→                  className="w-7 h-7 rounded-lg bg-red-500/15 hover:bg-red-500/25 flex items-center justify-center transition-colors"
    60→                  title="Reject"
    61→                >
    62→                  <X className="w-3.5 h-3.5 text-red-400" />
    63→                </button>
    64→

    65→

    66→          );
    67→        })}
    68→

    69→    </Card>
    70→  );
    71→}
    72→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

/**
 * PendingApprovals
 * ================
 * Shows tasks/actions that need Michael's approval.
 * Agents propose → this shows them → Michael approves/rejects inline.
 *
 * Props:
 *   approvals — array of { id, agent, action, description, createdAt }
 *   onApprove — (id) => void
 *   onReject  — (id) => void
 */
import Card, { CardHeader } from '../ui/Card';
import { ShieldCheck, Check, X } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

export default function PendingApprovals({ approvals = [], onApprove, onReject }) {
  // Demo data
  const displayApprovals = approvals.length > 0 ? approvals : [
    { id: 1, agent: 'builder', action: 'Deploy Growth Engine v2.4', description: 'New SMS retry logic + webhook hardening', createdAt: new Date(Date.now() - 600000) },
    { id: 2, agent: 'susan', action: 'Send batch SMS to 45 cold leads', description: 'Re-engagement campaign for leads dormant >30 days', createdAt: new Date(Date.now() - 1200000) },
    { id: 3, agent: 'cog', action: 'Upgrade Node.js to v22', description: 'Current v20 hitting memory limits with 8 agents', createdAt: new Date(Date.now() - 3600000) },
  ];

  if (displayApprovals.length === 0) return null;

  return (
    <Card>
      <CardHeader
        title="Pending Approvals"
        subtitle={`${displayApprovals.length} awaiting`}
        icon={ShieldCheck}
      />

        {displayApprovals.map((item) => {
          const agent = getAgent(item.agent);
          return (

              {agent?.avatar || '🤖'}

                  {item.action}

                  {agent?.name}
                  {' — '}
                  {item.description}

                <button
                  onClick={() => onApprove?.(item.id)}
                  className="w-7 h-7 rounded-lg bg-emerald-500/15 hover:bg-emerald-500/25 flex items-center justify-center transition-colors"
                  title="Approve"
                >
                  <Check className="w-3.5 h-3.5 text-emerald-400" />
                </button>
                <button
                  onClick={() => onReject?.(item.id)}
                  className="w-7 h-7 rounded-lg bg-red-500/15 hover:bg-red-500/25 flex items-center justify-center transition-colors"
                  title="Reject"
                >
                  <X className="w-3.5 h-3.5 text-red-400" />
                </button>

          );
        })}

    </Card>
  );
}


import Card, { CardHeader } from '../ui/Card';
import { ShieldCheck, Check, X } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

export default function PendingApprovals({ approvals = [], onApprove, onReject }) {
  // Demo data
  const displayApprovals = approvals.length > 0 ? approvals : [
    { id: 1, agent: 'builder', action: 'Deploy Growth Engine v2.4', description: 'New SMS retry logic + webhook hardening', createdAt: new Date(Date.now() - 600000) },
    { id: 2, agent: 'susan', action: 'Send batch SMS to 45 cold leads', description: 'Re-engagement campaign for leads dormant >30 days', createdAt: new Date(Date.now() - 1200000) },
    { id: 3, agent: 'cog', action: 'Upgrade Node.js to v22', description: 'Current v20 hitting memory limits with 8 agents', createdAt: new Date(Date.now() - 3600000) },
  ];

  if (displayApprovals.length === 0) return null;

import Card, { CardHeader } from '../ui/Card';
import { ShieldCheck, Check, X } from 'lucide-react';
import { getAgent } from '../../lib/fleet';
import { useApprovals } from '../../hooks/useSupabase';

export default function PendingApprovals({ approvals = [], onApprove, onReject }) {
  const { data: pendingApprovals } = useApprovals('pending');

  // Fallback demo data
  const FALLBACK_APPROVALS = [
    { id: 1, agent: 'builder', action: 'Deploy Growth Engine v2.4', description: 'New SMS retry logic + webhook hardening', createdAt: new Date(Date.now() - 600000) },
    { id: 2, agent: 'susan', action: 'Send batch SMS to 45 cold leads', description: 'Re-engagement campaign for leads dormant >30 days', createdAt: new Date(Date.now() - 1200000) },
    { id: 3, agent: 'cog', action: 'Upgrade Node.js to v22', description: 'Current v20 hitting memory limits with 8 agents', createdAt: new Date(Date.now() - 3600000) },
  ];

  // Map Supabase data to component format
  const mappedApprovals = pendingApprovals && pendingApprovals.length > 0
    ? pendingApprovals.map(a => ({
        id: a.id,
        agent: a.agent_id,
        action: a.action_title,
        description: a.action_details,
        createdAt: new Date(a.created_at),
      }))
    : [];

  const displayApprovals = approvals.length > 0 ? approvals : (mappedApprovals.length > 0 ? mappedApprovals : FALLBACK_APPROVALS);

  if (displayApprovals.length === 0) return null;


The file /home/ccuser/opsman-control-centre/src/components/dashboard/PendingApprovals.jsx has been updated successfully.

import Card, { CardHeader } from '../ui/Card';
import { ShieldCheck, Check, X } from 'lucide-react';
import { getAgent } from '../../lib/fleet';
import { useApprovals } from '../../hooks/useSupabase';

export default function PendingApprovals({ approvals = [], onApprove, onReject }) {
  const { data: pendingApprovals } = useApprovals('pending');

  // Fallback demo data
  const FALLBACK_APPROVALS = [
    { id: 1, agent: 'builder', action: 'Deploy Growth Engine v2.4', description: 'New SMS retry logic + webhook hardening', createdAt: new Date(Date.now() - 600000) },
    { id: 2, agent: 'susan', action: 'Send batch SMS to 45 cold leads', description: 'Re-engagement campaign for leads dormant >30 days', createdAt: new Date(Date.now() - 1200000) },
    { id: 3, agent: 'cog', action: 'Upgrade Node.js to v22', description: 'Current v20 hitting memory limits with 8 agents', createdAt: new Date(Date.now() - 3600000) },
  ];

  // Map Supabase data to component format
  const mappedApprovals = pendingApprovals && pendingApprovals.length > 0
    ? pendingApprovals.map(a => ({
        id: a.id,
        agent: a.agent_id,
        action: a.action_title,
        description: a.action_details,
        createdAt: new Date(a.created_at),
      }))
    : [];

  const displayApprovals = approvals.length > 0 ? approvals : (mappedApprovals.length > 0 ? mappedApprovals : FALLBACK_APPROVALS);

  if (displayApprovals.length === 0) return null;

/**
 * PendingApprovals
 * ================
 * Shows tasks/actions that need Michael's approval.
 * Agents propose → this shows them → Michael approves/rejects inline.
 *
 * Props:
 *   approvals — array of { id, agent, action, description, createdAt }
 *   onApprove — (id) => void
 *   onReject  — (id) => void
 */
import Card, { CardHeader } from '../ui/Card';
import { ShieldCheck, Check, X } from 'lucide-react';
import { getAgent } from '../../lib/fleet';

export default function PendingApprovals({ approvals = [], onApprove, onReject }) {
  // Demo data
  const displayApprovals = approvals.length > 0 ? approvals : [
    { id: 1, agent: 'builder', action: 'Deploy Growth Engine v2.4', description: 'New SMS retry logic + webhook hardening', createdAt: new Date(Date.now() - 600000) },
    { id: 2, agent: 'susan', action: 'Send batch SMS to 45 cold leads', description: 'Re-engagement campaign for leads dormant >30 days', createdAt: new Date(Date.now() - 1200000) },
    { id: 3, agent: 'cog', action: 'Upgrade Node.js to v22', description: 'Current v20 hitting memory limits with 8 agents', createdAt: new Date(Date.now() - 3600000) },
  ];

  if (displayApprovals.length === 0) return null;

  return (
    <Card>
      <CardHeader
        title="Pending Approvals"
        subtitle={`${displayApprovals.length} awaiting`}
        icon={ShieldCheck}
      />

        {displayApprovals.map((item) => {
          const agent = getAgent(item.agent);
          return (

              {agent?.avatar || '🤖'}

                  {item.action}

                  {agent?.name}
                  {' — '}
                  {item.description}

                <button
                  onClick={() => onApprove?.(item.id)}
                  className="w-7 h-7 rounded-lg bg-emerald-500/15 hover:bg-emerald-500/25 flex items-center justify-center transition-colors"
                  title="Approve"
                >
                  <Check className="w-3.5 h-3.5 text-emerald-400" />
                </button>
                <button
                  onClick={() => onReject?.(item.id)}
                  className="w-7 h-7 rounded-lg bg-red-500/15 hover:bg-red-500/25 flex items-center justify-center transition-colors"
                  title="Reject"
                >
                  <X className="w-3.5 h-3.5 text-red-400" />
                </button>

          );
        })}

    </Card>
  );
}

+import { useApprovals } from '../../hooks/useSupabase';

-  const displayApprovals = approvals.length > 0 ? approvals : [

+  const { data: pendingApprovals } = useApprovals('pending');

+  const mappedApprovals = pendingApprovals && pendingApprovals.length > 0

+        createdAt: new Date(a.created_at),

+  const displayApprovals = approvals.length > 0 ? approvals : (mappedApprovals.length > 0 ? mappedApprovals : FALLBACK_APPROVALS);


TodoWrite

Wire CommandCentre page to real Supabase data

Wiring CommandCentre page to real Supabase data

Wire Tasks page to real Supabase data with mutations

Wiring Tasks page to real Supabase data with mutations

Wire Council page to real Supabase data with realtime

Wiring Council page to real Supabase data with realtime

Wire Fleet page to real Supabase data

Wire Projects page to real Supabase data

Wiring Projects page to real Supabase data

Wire ActivityFeed and PendingApprovals components

Wiring ActivityFeed and PendingApprovals components

Test build passes

Commit all changes with proof summary

Committing all changes with proof summary


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Wire CommandCentre page to real Supabase data

Wiring CommandCentre page to real Supabase data

Wire Tasks page to real Supabase data with mutations

Wiring Tasks page to real Supabase data with mutations

Wire Council page to real Supabase data with realtime

Wiring Council page to real Supabase data with realtime

Wire Fleet page to real Supabase data

Wire Projects page to real Supabase data

Wiring Projects page to real Supabase data

Wire ActivityFeed and PendingApprovals components

Wiring ActivityFeed and PendingApprovals components

Test build passes

Commit all changes with proof summary

Committing all changes with proof summary

Wire Fleet page to real Supabase data

Test build passes

Commit all changes with proof summary


Now let's wire up the Tasks page with mutations:


Edit

import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';

import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';
import { useTasks, useCreateTask, useUpdateTask } from '../hooks/useSupabase';


The file /home/ccuser/opsman-control-centre/src/pages/Tasks.jsx has been updated successfully.

import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';
import { useTasks, useCreateTask, useUpdateTask } from '../hooks/useSupabase';

/**
 * Tasks — Kanban Board
 * ====================
 * Drag-and-drop kanban with columns: Backlog → In Progress → Review → Done
 * Filterable by agent, project, priority.
 *
 * DATA: cc_tasks table in Supabase (see CONTRIBUTING.md for schema)
 * API: tasksApi in src/lib/api.js
 *
 * REPLACING DEMO DATA:
 *   const { data: tasks } = useQuery({ queryKey: ['tasks'], queryFn: () => tasksApi.list() });
 *   Then group by status into columns.
 */
import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';

// ── Column definitions ────────────────────────────────────
const COLUMNS = [
  { id: 'backlog', label: 'Backlog', color: 'var(--cc-text-muted)' },
  { id: 'in_progress', label: 'In Progress', color: 'var(--cc-accent)' },
  { id: 'review', label: 'Review', color: 'var(--cc-warning)' },
  { id: 'done', label: 'Done', color: 'var(--cc-healthy)' },
];

const PRIORITY_STYLES = {
  urgent: { label: 'Urgent', variant: 'error' },
  high:   { label: 'High', variant: 'warning' },
  medium: { label: 'Medium', variant: 'default' },
  low:    { label: 'Low', variant: 'offline' },
};

// ── Demo tasks ────────────────────────────────────────────
const INITIAL_TASKS = [
  { id: 't1', title: 'Fix SMS retry logic for Twilio 429s', status: 'backlog', priority: 'high', agent: 'builder', project: 'growth-engine', description: 'SMS sends are failing when Twilio rate-limits us. Need exponential backoff.' },
  { id: 't2', title: 'Research Hipages Q1 pricing changes', status: 'backlog', priority: 'medium', agent: 'radar', project: 'rateright', description: 'Hipages raised Gold tier pricing. Analyze impact on our positioning.' },
  { id: 't3', title: 'Write LinkedIn content calendar for March', status: 'backlog', priority: 'low', agent: 'harper', project: 'growth-engine', description: '4 posts per week covering hiring tips, platform features, customer stories.' },
  { id: 't4', title: 'Deploy Control Centre to VPS', status: 'in_progress', priority: 'urgent', agent: 'builder', project: 'opsman', description: 'Build, deploy via PM2, configure Nginx reverse proxy on cc.rateright.com.au' },
  { id: 't5', title: 'Run nightly lead scan — batch #48', status: 'in_progress', priority: 'high', agent: 'susan', project: 'rateright', description: 'Process 800+ leads from scraped sources, enrich, score, assign.' },
  { id: 't6', title: 'Upgrade Node.js to v22', status: 'in_progress', priority: 'medium', agent: 'cog', project: 'opsman', description: 'Current v20 hitting memory limits with 8 concurrent agents.' },
  { id: 't7', title: 'Review Builder deploy plan for GE v2.4', status: 'review', priority: 'high', agent: 'rivet', project: 'growth-engine', description: 'Builder proposed SMS retry + webhook hardening. Needs approval before deploy.' },
  { id: 't8', title: 'Audit VPS security after Yakka threat', status: 'review', priority: 'urgent', agent: 'sentinel', project: 'opsman', description: 'Full port scan, check fail2ban rules, verify SSH key-only access.' },
  { id: 't9', title: 'Published Q1 competitor analysis', status: 'done', priority: 'medium', agent: 'radar', project: 'rateright' },
  { id: 't10', title: 'Fixed Telegram token rotation', status: 'done', priority: 'high', agent: 'cog', project: 'opsman' },
  { id: 't11', title: 'SMS batch #47 — 12 follow-ups sent', status: 'done', priority: 'medium', agent: 'susan', project: 'rateright' },
];

// ── Create Task Modal ─────────────────────────────────────
function CreateTaskModal({ onClose, onCreate }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [priority, setPriority] = useState('medium');
  const [agentId, setAgentId] = useState('');
  const [projectId, setProjectId] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  const handleCreate = () => {
    if (!title.trim()) return;
    onCreate({
      id: 't' + Date.now(),
      title: title.trim(),
      description: description.trim(),
      priority,
      agent: agentId,
      project: projectId,
      status: 'backlog',
    });
    onClose();
  };

  return (
    <>

Create Task

          <button onClick={onClose} className="p-1 rounded hover:bg-[var(--cc-surface-hover)]">
            <X className="w-4 h-4 text-[var(--cc-text-muted)]" />
          </button>

          <input
            type="text"
            placeholder="Task title..."
            value={title}
            onChange={e => setTitle(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && handleCreate()}
            autoFocus
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)]"
          />
          <textarea
            placeholder="Description (optional)..."
            value={description}
            onChange={e => setDescription(e.target.value)}
            rows={3}
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)] resize-none"
          />

            <select value={priority} onChange={e => setPriority(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="low">Low</option>
              <option value="medium">Medium</option>
              <option value="high">High</option>
              <option value="urgent">Urgent</option>
            </select>
            <select value={agentId} onChange={e => setAgentId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Agent...</option>
              {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
            </select>
            <select value={projectId} onChange={e => setProjectId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Project...</option>
              {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>

          <button onClick={onClose} className="px-3 py-1.5 text-[12px] text-[var(--cc-text-secondary)] hover:text-[var(--cc-text)] transition-colors">
            Cancel
          </button>
          <button
            onClick={handleCreate}
            disabled={!title.trim()}
            className="px-4 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors disabled:opacity-40"
          >
            Create
          </button>

    </>
  );
}

// ── Task Card (draggable) ─────────────────────────────────
function TaskCard({ task, index }) {
  const agent = getAgent(task.agent);
  const priorityStyle = PRIORITY_STYLES[task.priority] || PRIORITY_STYLES.medium;

  return (
    <Draggable draggableId={task.id} index={index}>
      {(provided, snapshot) => (

          {/* Drag handle + priority */}

              <GripVertical className="w-3 h-3 text-[var(--cc-text-muted)]" />

              {task.title}

          {/* Meta row */}

            <Badge variant={priorityStyle.variant}>{priorityStyle.label}</Badge>
            {agent && (

                {agent.avatar}
                {agent.name}

            )}

      )}
    </Draggable>
  );
}

// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [tasks, setTasks] = useState(INITIAL_TASKS);
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Filter tasks
  const filtered = tasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });

  // Group by column
  const columns = COLUMNS.map(col => ({
    ...col,
    tasks: filtered.filter(t => t.status === col.id),
  }));

  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    setTasks(prev => prev.map(t =>
      t.id === draggableId ? { ...t, status: destination.droppableId } : t
    ));
    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })
  }, []);

  // Handle create
  const handleCreate = (newTask) => {
    setTasks(prev => [newTask, ...prev]);
    // TODO: tasksApi.create(newTask)
  };

  return (

      {/* Toolbar */}

          <Filter className="w-4 h-4 text-[var(--cc-text-muted)]" />
          <select
            value={filterAgent}
            onChange={e => setFilterAgent(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All agents</option>
            {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
          </select>
          <select
            value={filterProject}
            onChange={e => setFilterProject(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All projects</option>
            {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
          </select>

        <button
          onClick={() => setShowCreate(true)}
          className="flex items-center gap-1.5 px-3 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors"
        >
          <Plus className="w-3.5 h-3.5" />
          New Task
        </button>

      {/* Kanban Board */}
      <DragDropContext onDragEnd={onDragEnd}>

          {columns.map((col) => (

              {/* Column header */}

                  {col.label}

                  {col.tasks.length}

              {/* Droppable column */}
              <Droppable droppableId={col.id}>
                {(provided, snapshot) => (

                    {col.tasks.map((task, index) => (
                      <TaskCard key={task.id} task={task} index={index} />
                    ))}
                    {provided.placeholder}
                    {col.tasks.length === 0 && (

                        No tasks

                    )}

                )}
              </Droppable>

          ))}

      </DragDropContext>

      {/* Create Modal */}
      {showCreate && (
        <CreateTaskModal
          onClose={() => setShowCreate(false)}
          onCreate={handleCreate}
        />
      )}

  );
}

+import { useTasks, useCreateTask, useUpdateTask } from '../hooks/useSupabase';


// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [tasks, setTasks] = useState(INITIAL_TASKS);
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Filter tasks
  const filtered = tasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });

// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Fetch tasks from Supabase
  const { data: tasksData } = useTasks();
  const createTaskMutation = useCreateTask();
  const updateTaskMutation = useUpdateTask();

  // Map Supabase data to UI format (fallback to demo if empty)
  const mappedTasks = tasksData && tasksData.length > 0
    ? tasksData.map(t => ({
        id: t.id,
        title: t.title,
        status: t.status,
        priority: t.priority || 'medium',
        agent: t.agent_id,
        project: t.project_id,
        description: t.description,
      }))
    : INITIAL_TASKS;

  // Filter tasks
  const filtered = mappedTasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });


// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Fetch tasks from Supabase
  const { data: tasksData } = useTasks();
  const createTaskMutation = useCreateTask();
  const updateTaskMutation = useUpdateTask();

  // Map Supabase data to UI format (fallback to demo if empty)
  const mappedTasks = tasksData && tasksData.length > 0
    ? tasksData.map(t => ({
        id: t.id,
        title: t.title,
        status: t.status,
        priority: t.priority || 'medium',
        agent: t.agent_id,
        project: t.project_id,
        description: t.description,
      }))
    : INITIAL_TASKS;

  // Filter tasks
  const filtered = mappedTasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });

/**
 * Tasks — Kanban Board
 * ====================
 * Drag-and-drop kanban with columns: Backlog → In Progress → Review → Done
 * Filterable by agent, project, priority.
 *
 * DATA: cc_tasks table in Supabase (see CONTRIBUTING.md for schema)
 * API: tasksApi in src/lib/api.js
 *
 * REPLACING DEMO DATA:
 *   const { data: tasks } = useQuery({ queryKey: ['tasks'], queryFn: () => tasksApi.list() });
 *   Then group by status into columns.
 */
import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';
import { useTasks, useCreateTask, useUpdateTask } from '../hooks/useSupabase';

// ── Column definitions ────────────────────────────────────
const COLUMNS = [
  { id: 'backlog', label: 'Backlog', color: 'var(--cc-text-muted)' },
  { id: 'in_progress', label: 'In Progress', color: 'var(--cc-accent)' },
  { id: 'review', label: 'Review', color: 'var(--cc-warning)' },
  { id: 'done', label: 'Done', color: 'var(--cc-healthy)' },
];

const PRIORITY_STYLES = {
  urgent: { label: 'Urgent', variant: 'error' },
  high:   { label: 'High', variant: 'warning' },
  medium: { label: 'Medium', variant: 'default' },
  low:    { label: 'Low', variant: 'offline' },
};

// ── Demo tasks ────────────────────────────────────────────
const INITIAL_TASKS = [
  { id: 't1', title: 'Fix SMS retry logic for Twilio 429s', status: 'backlog', priority: 'high', agent: 'builder', project: 'growth-engine', description: 'SMS sends are failing when Twilio rate-limits us. Need exponential backoff.' },
  { id: 't2', title: 'Research Hipages Q1 pricing changes', status: 'backlog', priority: 'medium', agent: 'radar', project: 'rateright', description: 'Hipages raised Gold tier pricing. Analyze impact on our positioning.' },
  { id: 't3', title: 'Write LinkedIn content calendar for March', status: 'backlog', priority: 'low', agent: 'harper', project: 'growth-engine', description: '4 posts per week covering hiring tips, platform features, customer stories.' },
  { id: 't4', title: 'Deploy Control Centre to VPS', status: 'in_progress', priority: 'urgent', agent: 'builder', project: 'opsman', description: 'Build, deploy via PM2, configure Nginx reverse proxy on cc.rateright.com.au' },
  { id: 't5', title: 'Run nightly lead scan — batch #48', status: 'in_progress', priority: 'high', agent: 'susan', project: 'rateright', description: 'Process 800+ leads from scraped sources, enrich, score, assign.' },
  { id: 't6', title: 'Upgrade Node.js to v22', status: 'in_progress', priority: 'medium', agent: 'cog', project: 'opsman', description: 'Current v20 hitting memory limits with 8 concurrent agents.' },
  { id: 't7', title: 'Review Builder deploy plan for GE v2.4', status: 'review', priority: 'high', agent: 'rivet', project: 'growth-engine', description: 'Builder proposed SMS retry + webhook hardening. Needs approval before deploy.' },
  { id: 't8', title: 'Audit VPS security after Yakka threat', status: 'review', priority: 'urgent', agent: 'sentinel', project: 'opsman', description: 'Full port scan, check fail2ban rules, verify SSH key-only access.' },
  { id: 't9', title: 'Published Q1 competitor analysis', status: 'done', priority: 'medium', agent: 'radar', project: 'rateright' },
  { id: 't10', title: 'Fixed Telegram token rotation', status: 'done', priority: 'high', agent: 'cog', project: 'opsman' },
  { id: 't11', title: 'SMS batch #47 — 12 follow-ups sent', status: 'done', priority: 'medium', agent: 'susan', project: 'rateright' },
];

// ── Create Task Modal ─────────────────────────────────────
function CreateTaskModal({ onClose, onCreate }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [priority, setPriority] = useState('medium');
  const [agentId, setAgentId] = useState('');
  const [projectId, setProjectId] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  const handleCreate = () => {
    if (!title.trim()) return;
    onCreate({
      id: 't' + Date.now(),
      title: title.trim(),
      description: description.trim(),
      priority,
      agent: agentId,
      project: projectId,
      status: 'backlog',
    });
    onClose();
  };

  return (
    <>

Create Task

          <button onClick={onClose} className="p-1 rounded hover:bg-[var(--cc-surface-hover)]">
            <X className="w-4 h-4 text-[var(--cc-text-muted)]" />
          </button>

          <input
            type="text"
            placeholder="Task title..."
            value={title}
            onChange={e => setTitle(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && handleCreate()}
            autoFocus
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)]"
          />
          <textarea
            placeholder="Description (optional)..."
            value={description}
            onChange={e => setDescription(e.target.value)}
            rows={3}
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)] resize-none"
          />

            <select value={priority} onChange={e => setPriority(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="low">Low</option>
              <option value="medium">Medium</option>
              <option value="high">High</option>
              <option value="urgent">Urgent</option>
            </select>
            <select value={agentId} onChange={e => setAgentId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Agent...</option>
              {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
            </select>
            <select value={projectId} onChange={e => setProjectId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Project...</option>
              {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>

          <button onClick={onClose} className="px-3 py-1.5 text-[12px] text-[var(--cc-text-secondary)] hover:text-[var(--cc-text)] transition-colors">
            Cancel
          </button>
          <button
            onClick={handleCreate}
            disabled={!title.trim()}
            className="px-4 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors disabled:opacity-40"
          >
            Create
          </button>

    </>
  );
}

// ── Task Card (draggable) ─────────────────────────────────
function TaskCard({ task, index }) {
  const agent = getAgent(task.agent);
  const priorityStyle = PRIORITY_STYLES[task.priority] || PRIORITY_STYLES.medium;

  return (
    <Draggable draggableId={task.id} index={index}>
      {(provided, snapshot) => (

          {/* Drag handle + priority */}

              <GripVertical className="w-3 h-3 text-[var(--cc-text-muted)]" />

              {task.title}

          {/* Meta row */}

            <Badge variant={priorityStyle.variant}>{priorityStyle.label}</Badge>
            {agent && (

                {agent.avatar}
                {agent.name}

            )}

      )}
    </Draggable>
  );
}

// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [tasks, setTasks] = useState(INITIAL_TASKS);
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Filter tasks
  const filtered = tasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });

  // Group by column
  const columns = COLUMNS.map(col => ({
    ...col,
    tasks: filtered.filter(t => t.status === col.id),
  }));

  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    setTasks(prev => prev.map(t =>
      t.id === draggableId ? { ...t, status: destination.droppableId } : t
    ));
    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })
  }, []);

  // Handle create
  const handleCreate = (newTask) => {
    setTasks(prev => [newTask, ...prev]);
    // TODO: tasksApi.create(newTask)
  };

  return (

      {/* Toolbar */}

          <Filter className="w-4 h-4 text-[var(--cc-text-muted)]" />
          <select
            value={filterAgent}
            onChange={e => setFilterAgent(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All agents</option>
            {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
          </select>
          <select
            value={filterProject}
            onChange={e => setFilterProject(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All projects</option>
            {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
          </select>

        <button
          onClick={() => setShowCreate(true)}
          className="flex items-center gap-1.5 px-3 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors"
        >
          <Plus className="w-3.5 h-3.5" />
          New Task
        </button>

      {/* Kanban Board */}
      <DragDropContext onDragEnd={onDragEnd}>

          {columns.map((col) => (

              {/* Column header */}

                  {col.label}

                  {col.tasks.length}

              {/* Droppable column */}
              <Droppable droppableId={col.id}>
                {(provided, snapshot) => (

                    {col.tasks.map((task, index) => (
                      <TaskCard key={task.id} task={task} index={index} />
                    ))}
                    {provided.placeholder}
                    {col.tasks.length === 0 && (

                        No tasks

                    )}

                )}
              </Droppable>

          ))}

      </DragDropContext>

      {/* Create Modal */}
      {showCreate && (
        <CreateTaskModal
          onClose={() => setShowCreate(false)}
          onCreate={handleCreate}
        />
      )}

  );
}

-  const [tasks, setTasks] = useState(INITIAL_TASKS);

+  const { data: tasksData } = useTasks();

+  const createTaskMutation = useCreateTask();

+  const updateTaskMutation = useUpdateTask();

+  // Map Supabase data to UI format (fallback to demo if empty)

+  const mappedTasks = tasksData && tasksData.length > 0

+        priority: t.priority || 'medium',

+  const filtered = mappedTasks.filter(t => {


  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    setTasks(prev => prev.map(t =>
      t.id === draggableId ? { ...t, status: destination.droppableId } : t
    ));
    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })
  }, []);

  // Handle create
  const handleCreate = (newTask) => {
    setTasks(prev => [newTask, ...prev]);
    // TODO: tasksApi.create(newTask)
  };

  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    // Update in Supabase
    updateTaskMutation.mutate({
      id: draggableId,
      status: destination.droppableId,
    });
  }, [updateTaskMutation]);

  // Handle create
  const handleCreate = (newTask) => {
    createTaskMutation.mutate({
      title: newTask.title,
      description: newTask.description,
      priority: newTask.priority,
      agent_id: newTask.agent,
      project_id: newTask.project,
      status: 'backlog',
    });
  };


  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    // Update in Supabase
    updateTaskMutation.mutate({
      id: draggableId,
      status: destination.droppableId,
    });
  }, [updateTaskMutation]);

  // Handle create
  const handleCreate = (newTask) => {
    createTaskMutation.mutate({
      title: newTask.title,
      description: newTask.description,
      priority: newTask.priority,
      agent_id: newTask.agent,
      project_id: newTask.project,
      status: 'backlog',
    });
  };

/**
 * Tasks — Kanban Board
 * ====================
 * Drag-and-drop kanban with columns: Backlog → In Progress → Review → Done
 * Filterable by agent, project, priority.
 *
 * DATA: cc_tasks table in Supabase (see CONTRIBUTING.md for schema)
 * API: tasksApi in src/lib/api.js
 *
 * REPLACING DEMO DATA:
 *   const { data: tasks } = useQuery({ queryKey: ['tasks'], queryFn: () => tasksApi.list() });
 *   Then group by status into columns.
 */
import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';
import { useTasks, useCreateTask, useUpdateTask } from '../hooks/useSupabase';

// ── Column definitions ────────────────────────────────────
const COLUMNS = [
  { id: 'backlog', label: 'Backlog', color: 'var(--cc-text-muted)' },
  { id: 'in_progress', label: 'In Progress', color: 'var(--cc-accent)' },
  { id: 'review', label: 'Review', color: 'var(--cc-warning)' },
  { id: 'done', label: 'Done', color: 'var(--cc-healthy)' },
];

const PRIORITY_STYLES = {
  urgent: { label: 'Urgent', variant: 'error' },
  high:   { label: 'High', variant: 'warning' },
  medium: { label: 'Medium', variant: 'default' },
  low:    { label: 'Low', variant: 'offline' },
};

// ── Demo tasks ────────────────────────────────────────────
const INITIAL_TASKS = [
  { id: 't1', title: 'Fix SMS retry logic for Twilio 429s', status: 'backlog', priority: 'high', agent: 'builder', project: 'growth-engine', description: 'SMS sends are failing when Twilio rate-limits us. Need exponential backoff.' },
  { id: 't2', title: 'Research Hipages Q1 pricing changes', status: 'backlog', priority: 'medium', agent: 'radar', project: 'rateright', description: 'Hipages raised Gold tier pricing. Analyze impact on our positioning.' },
  { id: 't3', title: 'Write LinkedIn content calendar for March', status: 'backlog', priority: 'low', agent: 'harper', project: 'growth-engine', description: '4 posts per week covering hiring tips, platform features, customer stories.' },
  { id: 't4', title: 'Deploy Control Centre to VPS', status: 'in_progress', priority: 'urgent', agent: 'builder', project: 'opsman', description: 'Build, deploy via PM2, configure Nginx reverse proxy on cc.rateright.com.au' },
  { id: 't5', title: 'Run nightly lead scan — batch #48', status: 'in_progress', priority: 'high', agent: 'susan', project: 'rateright', description: 'Process 800+ leads from scraped sources, enrich, score, assign.' },
  { id: 't6', title: 'Upgrade Node.js to v22', status: 'in_progress', priority: 'medium', agent: 'cog', project: 'opsman', description: 'Current v20 hitting memory limits with 8 concurrent agents.' },
  { id: 't7', title: 'Review Builder deploy plan for GE v2.4', status: 'review', priority: 'high', agent: 'rivet', project: 'growth-engine', description: 'Builder proposed SMS retry + webhook hardening. Needs approval before deploy.' },
  { id: 't8', title: 'Audit VPS security after Yakka threat', status: 'review', priority: 'urgent', agent: 'sentinel', project: 'opsman', description: 'Full port scan, check fail2ban rules, verify SSH key-only access.' },
  { id: 't9', title: 'Published Q1 competitor analysis', status: 'done', priority: 'medium', agent: 'radar', project: 'rateright' },
  { id: 't10', title: 'Fixed Telegram token rotation', status: 'done', priority: 'high', agent: 'cog', project: 'opsman' },
  { id: 't11', title: 'SMS batch #47 — 12 follow-ups sent', status: 'done', priority: 'medium', agent: 'susan', project: 'rateright' },
];

// ── Create Task Modal ─────────────────────────────────────
function CreateTaskModal({ onClose, onCreate }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [priority, setPriority] = useState('medium');
  const [agentId, setAgentId] = useState('');
  const [projectId, setProjectId] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  const handleCreate = () => {
    if (!title.trim()) return;
    onCreate({
      id: 't' + Date.now(),
      title: title.trim(),
      description: description.trim(),
      priority,
      agent: agentId,
      project: projectId,
      status: 'backlog',
    });
    onClose();
  };

  return (
    <>

Create Task

          <button onClick={onClose} className="p-1 rounded hover:bg-[var(--cc-surface-hover)]">
            <X className="w-4 h-4 text-[var(--cc-text-muted)]" />
          </button>

          <input
            type="text"
            placeholder="Task title..."
            value={title}
            onChange={e => setTitle(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && handleCreate()}
            autoFocus
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)]"
          />
          <textarea
            placeholder="Description (optional)..."
            value={description}
            onChange={e => setDescription(e.target.value)}
            rows={3}
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)] resize-none"
          />

            <select value={priority} onChange={e => setPriority(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="low">Low</option>
              <option value="medium">Medium</option>
              <option value="high">High</option>
              <option value="urgent">Urgent</option>
            </select>
            <select value={agentId} onChange={e => setAgentId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Agent...</option>
              {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
            </select>
            <select value={projectId} onChange={e => setProjectId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Project...</option>
              {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>

          <button onClick={onClose} className="px-3 py-1.5 text-[12px] text-[var(--cc-text-secondary)] hover:text-[var(--cc-text)] transition-colors">
            Cancel
          </button>
          <button
            onClick={handleCreate}
            disabled={!title.trim()}
            className="px-4 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors disabled:opacity-40"
          >
            Create
          </button>

    </>
  );
}

// ── Task Card (draggable) ─────────────────────────────────
function TaskCard({ task, index }) {
  const agent = getAgent(task.agent);
  const priorityStyle = PRIORITY_STYLES[task.priority] || PRIORITY_STYLES.medium;

  return (
    <Draggable draggableId={task.id} index={index}>
      {(provided, snapshot) => (

          {/* Drag handle + priority */}

              <GripVertical className="w-3 h-3 text-[var(--cc-text-muted)]" />

              {task.title}

          {/* Meta row */}

            <Badge variant={priorityStyle.variant}>{priorityStyle.label}</Badge>
            {agent && (

                {agent.avatar}
                {agent.name}

            )}

      )}
    </Draggable>
  );
}

// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Fetch tasks from Supabase
  const { data: tasksData } = useTasks();
  const createTaskMutation = useCreateTask();
  const updateTaskMutation = useUpdateTask();

  // Map Supabase data to UI format (fallback to demo if empty)
  const mappedTasks = tasksData && tasksData.length > 0
    ? tasksData.map(t => ({
        id: t.id,
        title: t.title,
        status: t.status,
        priority: t.priority || 'medium',
        agent: t.agent_id,
        project: t.project_id,
        description: t.description,
      }))
    : INITIAL_TASKS;

  // Filter tasks
  const filtered = mappedTasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });

  // Group by column
  const columns = COLUMNS.map(col => ({
    ...col,
    tasks: filtered.filter(t => t.status === col.id),
  }));

  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    setTasks(prev => prev.map(t =>
      t.id === draggableId ? { ...t, status: destination.droppableId } : t
    ));
    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })
  }, []);

  // Handle create
  const handleCreate = (newTask) => {
    setTasks(prev => [newTask, ...prev]);
    // TODO: tasksApi.create(newTask)
  };

  return (

      {/* Toolbar */}

          <Filter className="w-4 h-4 text-[var(--cc-text-muted)]" />
          <select
            value={filterAgent}
            onChange={e => setFilterAgent(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All agents</option>
            {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
          </select>
          <select
            value={filterProject}
            onChange={e => setFilterProject(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All projects</option>
            {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
          </select>

        <button
          onClick={() => setShowCreate(true)}
          className="flex items-center gap-1.5 px-3 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors"
        >
          <Plus className="w-3.5 h-3.5" />
          New Task
        </button>

      {/* Kanban Board */}
      <DragDropContext onDragEnd={onDragEnd}>

          {columns.map((col) => (

              {/* Column header */}

                  {col.label}

                  {col.tasks.length}

              {/* Droppable column */}
              <Droppable droppableId={col.id}>
                {(provided, snapshot) => (

                    {col.tasks.map((task, index) => (
                      <TaskCard key={task.id} task={task} index={index} />
                    ))}
                    {provided.placeholder}
                    {col.tasks.length === 0 && (

                        No tasks

                    )}

                )}
              </Droppable>

          ))}

      </DragDropContext>

      {/* Create Modal */}
      {showCreate && (
        <CreateTaskModal
          onClose={() => setShowCreate(false)}
          onCreate={handleCreate}
        />
      )}

  );
}

-      t.id === draggableId ? { ...t, status: destination.droppableId } : t

-    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })

-    setTasks(prev => [newTask, ...prev]);

+      description: newTask.description,


TodoWrite

Wire CommandCentre page to real Supabase data

Wiring CommandCentre page to real Supabase data

Wire Tasks page to real Supabase data with mutations

Wiring Tasks page to real Supabase data with mutations

Wire Council page to real Supabase data with realtime

Wiring Council page to real Supabase data with realtime

Wire Fleet page to real Supabase data

Wire Projects page to real Supabase data

Wiring Projects page to real Supabase data

Wire ActivityFeed and PendingApprovals components

Wiring ActivityFeed and PendingApprovals components

Test build passes

Commit all changes with proof summary

Committing all changes with proof summary


Wire Fleet page to real Supabase data

Test build passes

Commit all changes with proof summary

Wire Fleet page to real Supabase data

Test build passes

Commit all changes with proof summary


Now let's wire up Council with realtime subscriptions. First, I need to add realtime subscription support to the hooks:


Edit

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '../lib/supabase';

function ensureSupabase() {
  if (!supabase) throw new Error('Supabase client not configured');
  return supabase;
}

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { supabase } from '../lib/supabase';

function ensureSupabase() {
  if (!supabase) throw new Error('Supabase client not configured');
  return supabase;
}